mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +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 Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
@ -10,8 +11,7 @@ namespace BTCPayServer.Client.Models
|
|||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public string Metadata { get; set; }
|
||||
public string CustomerEmail { get; set; }
|
||||
public JObject Metadata { get; set; }
|
||||
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
|
||||
|
||||
public class CheckoutOptions
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);ALTCOINS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
|
|
|
@ -10,6 +10,7 @@ using BTCPayServer.Controllers;
|
|||
using BTCPayServer.Events;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -17,6 +18,7 @@ using NBitcoin;
|
|||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUglify.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||
|
@ -731,8 +733,75 @@ 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)]
|
||||
[Trait("Integration", "Integration")]
|
||||
|
@ -750,11 +819,11 @@ namespace BTCPayServer.Tests
|
|||
//create
|
||||
|
||||
//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 client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions(){ PaymentTolerance = -2, PaymentMethods = new []{"jasaas_sdsad"}}});
|
||||
});
|
||||
|
||||
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 AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.CreateInvoice(user.StoreId,
|
||||
|
@ -762,7 +831,7 @@ namespace BTCPayServer.Tests
|
|||
});
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
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
|
||||
var invoices = await viewOnly.GetInvoices(user.StoreId);
|
||||
|
@ -788,15 +857,7 @@ namespace BTCPayServer.Tests
|
|||
Email = "j@g.com"
|
||||
});
|
||||
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 client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
||||
|
@ -804,8 +865,8 @@ namespace BTCPayServer.Tests
|
|||
Status = InvoiceStatus.Complete
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
//archive
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
|
@ -815,15 +876,15 @@ namespace BTCPayServer.Tests
|
|||
await client.ArchiveInvoice(user.StoreId, invoice.Id);
|
||||
Assert.DoesNotContain(invoice.Id,
|
||||
(await client.GetInvoices(user.StoreId)).Select(data => data.Id));
|
||||
|
||||
|
||||
//unarchive
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
|
@ -855,10 +916,11 @@ namespace BTCPayServer.Tests
|
|||
Assert.Throws<JsonSerializationException>(() =>
|
||||
{
|
||||
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.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
|
||||
|
|
|
@ -53,7 +53,6 @@ using Newtonsoft.Json.Schema;
|
|||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
||||
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
|
@ -326,7 +325,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.True(Torrc.TryParse(input, out torrc));
|
||||
Assert.Equal(expected, torrc.ToString());
|
||||
}
|
||||
|
||||
#if ALTCOINS
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanCalculateCryptoDue()
|
||||
|
@ -347,7 +346,7 @@ namespace BTCPayServer.Tests
|
|||
Rate = 5000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
});
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
entity.Price = 5000;
|
||||
|
||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||
var accounting = paymentMethod.Calculate();
|
||||
|
@ -397,7 +396,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
entity.Price = 5000;
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() { CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
||||
|
@ -491,7 +490,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
#endif
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseTestWebsiteUI()
|
||||
|
@ -546,7 +545,7 @@ namespace BTCPayServer.Tests
|
|||
Rate = 5000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
});
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
entity.Price = 5000;
|
||||
entity.PaymentTolerance = 0;
|
||||
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ namespace BTCPayServer.Controllers
|
|||
var store = await _AppService.GetStore(app);
|
||||
try
|
||||
{
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||
{
|
||||
ItemCode = choice?.Id,
|
||||
ItemDesc = title,
|
||||
|
@ -317,7 +317,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
try
|
||||
{
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||
{
|
||||
OrderId = AppService.GetCrowdfundOrderId(appId),
|
||||
Currency = settings.TargetCurrency,
|
||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Validation;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -48,7 +49,8 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
var invoices =
|
||||
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] {store.Id}, IncludeArchived = includeArchived
|
||||
StoreId = new[] { store.Id },
|
||||
IncludeArchived = includeArchived
|
||||
});
|
||||
|
||||
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)
|
||||
{
|
||||
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.ExpirationTime,
|
||||
|
@ -211,7 +206,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
request.AddModelError(invoiceRequest => invoiceRequest.Email, "Invalid email address",
|
||||
this);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(invoice.BuyerInformation.BuyerEmail))
|
||||
else if (!string.IsNullOrEmpty(invoice.Metadata.BuyerEmail))
|
||||
{
|
||||
request.AddModelError(invoiceRequest => invoiceRequest.Email, "Email address already set",
|
||||
this);
|
||||
|
@ -220,7 +215,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
if (!ModelState.IsValid)
|
||||
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);
|
||||
}
|
||||
|
@ -260,13 +255,12 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
return new InvoiceData()
|
||||
{
|
||||
Amount = entity.ProductInformation.Price,
|
||||
Amount = entity.Price,
|
||||
Id = entity.Id,
|
||||
Status = entity.Status,
|
||||
AdditionalStatus = entity.ExceptionStatus,
|
||||
Currency = entity.ProductInformation.Currency,
|
||||
Metadata = entity.PosData,
|
||||
CustomerEmail = entity.RefundMail ?? entity.BuyerInformation.BuyerEmail,
|
||||
Currency = entity.Currency,
|
||||
Metadata = entity.Metadata.ToJObject(),
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
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;
|
||||
ProductInformation pi = null;
|
||||
JToken? orderId = null;
|
||||
if (!string.IsNullOrEmpty(entity.Metadata) && entity.Metadata.StartsWith('{'))
|
||||
InvoiceMetadata invoiceMetadata = null;
|
||||
if (entity.Metadata != null)
|
||||
{
|
||||
//metadata was provided and is json. Let's try and match props
|
||||
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
|
||||
}
|
||||
invoiceMetadata = entity.Metadata.ToObject<InvoiceMetadata>();
|
||||
}
|
||||
|
||||
return new Models.CreateInvoiceRequest()
|
||||
return new Models.BitpayCreateInvoiceRequest()
|
||||
{
|
||||
Buyer = buyer,
|
||||
BuyerEmail = entity.CustomerEmail,
|
||||
Buyer = invoiceMetadata == null ? null : new Buyer()
|
||||
{
|
||||
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,
|
||||
Price = entity.Amount,
|
||||
Refundable = true,
|
||||
|
@ -360,12 +350,13 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
TransactionSpeed = entity.Checkout.SpeedPolicy?.ToString(),
|
||||
PaymentCurrencies = entity.Checkout.PaymentMethods,
|
||||
NotificationURL = entity.Checkout.RedirectUri,
|
||||
PosData = entity.Metadata,
|
||||
Physical = pi?.Physical ?? false,
|
||||
ItemCode = pi?.ItemCode,
|
||||
ItemDesc = pi?.ItemDesc,
|
||||
TaxIncluded = pi?.TaxIncluded,
|
||||
OrderId = orderId?.ToString()
|
||||
PosData = invoiceMetadata?.PosData,
|
||||
Physical = invoiceMetadata?.Physical ?? false,
|
||||
ItemCode = invoiceMetadata?.ItemCode,
|
||||
ItemDesc = invoiceMetadata?.ItemDesc,
|
||||
TaxIncluded = invoiceMetadata?.TaxIncluded,
|
||||
OrderId = invoiceMetadata?.OrderId,
|
||||
Metadata = entity.Metadata
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace BTCPayServer.Controllers
|
|||
[HttpPost]
|
||||
[Route("invoices")]
|
||||
[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)
|
||||
throw new BitpayHttpException(400, "Invalid invoice");
|
||||
|
|
|
@ -30,8 +30,7 @@ using NBitcoin;
|
|||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CreateInvoiceRequest = BTCPayServer.Models.CreateInvoiceRequest;
|
||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
||||
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
|
@ -54,7 +53,6 @@ namespace BTCPayServer.Controllers
|
|||
if (invoice == null)
|
||||
return NotFound();
|
||||
|
||||
var prodInfo = invoice.ProductInformation;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var model = new InvoiceDetailsModel()
|
||||
{
|
||||
|
@ -70,16 +68,14 @@ namespace BTCPayServer.Controllers
|
|||
CreatedDate = invoice.InvoiceTime,
|
||||
ExpirationDate = invoice.ExpirationTime,
|
||||
MonitoringDate = invoice.MonitoringExpiration,
|
||||
OrderId = invoice.OrderId,
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency(prodInfo.Price, prodInfo.Currency),
|
||||
TaxIncluded = _CurrencyNameTable.DisplayFormatCurrency(prodInfo.TaxIncluded, prodInfo.Currency),
|
||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency),
|
||||
TaxIncluded = _CurrencyNameTable.DisplayFormatCurrency(invoice.Metadata.TaxIncluded ?? 0.0m, invoice.Currency),
|
||||
NotificationUrl = invoice.NotificationURL?.AbsoluteUri,
|
||||
RedirectUrl = invoice.RedirectURL?.AbsoluteUri,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
TypedMetadata = invoice.Metadata,
|
||||
StatusException = invoice.ExceptionStatus,
|
||||
Events = invoice.Events,
|
||||
PosData = PosDataParser.ParsePosData(invoice.PosData),
|
||||
PosData = PosDataParser.ParsePosData(invoice.Metadata.PosData),
|
||||
Archived = invoice.Archived,
|
||||
CanRefund = CanRefund(invoice.GetInvoiceState()),
|
||||
};
|
||||
|
@ -179,7 +175,7 @@ namespace BTCPayServer.Controllers
|
|||
if (!CanRefund(invoice.GetInvoiceState()))
|
||||
return NotFound();
|
||||
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;
|
||||
if (model.SelectedRefundOption is null)
|
||||
{
|
||||
|
@ -189,7 +185,7 @@ namespace BTCPayServer.Controllers
|
|||
model.CryptoAmountThen = Math.Round(paidCurrency / paymentMethod.Rate, paymentMethodDivisibility);
|
||||
model.RateThenText = _CurrencyNameTable.DisplayFormatCurrency(model.CryptoAmountThen, paymentMethodId.CryptoCode, true);
|
||||
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?
|
||||
if (rateResult.BidAsk is null)
|
||||
{
|
||||
|
@ -199,7 +195,7 @@ namespace BTCPayServer.Controllers
|
|||
model.CryptoAmountNow = Math.Round(paidCurrency / rateResult.BidAsk.Bid, paymentMethodDivisibility);
|
||||
model.CurrentRateText = _CurrencyNameTable.DisplayFormatCurrency(model.CryptoAmountNow, paymentMethodId.CryptoCode, true);
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -219,7 +215,7 @@ namespace BTCPayServer.Controllers
|
|||
createPullPayment.Amount = model.CryptoAmountNow;
|
||||
break;
|
||||
case "Fiat":
|
||||
createPullPayment.Currency = invoice.ProductInformation.Currency;
|
||||
createPullPayment.Currency = invoice.Currency;
|
||||
createPullPayment.Amount = model.FiatAmount;
|
||||
break;
|
||||
default:
|
||||
|
@ -405,7 +401,7 @@ namespace BTCPayServer.Controllers
|
|||
var dto = invoice.EntityToDTO();
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var currency = invoice.ProductInformation.Currency;
|
||||
var currency = invoice.Currency;
|
||||
var accounting = paymentMethod.Calculate();
|
||||
|
||||
ChangellySettings changelly = (storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled &&
|
||||
|
@ -427,12 +423,11 @@ namespace BTCPayServer.Controllers
|
|||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||
|
||||
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
|
||||
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
CryptoCode = network.CryptoCode,
|
||||
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
|
||||
OrderId = invoice.OrderId,
|
||||
OrderId = invoice.Metadata.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
DefaultLang = storeBlob.DefaultLang ?? "en",
|
||||
CustomCSSLink = storeBlob.CustomCSS,
|
||||
|
@ -442,7 +437,7 @@ namespace BTCPayServer.Controllers
|
|||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||
BtcDue = accounting.Due.ShowMoney(divisibility),
|
||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ShowMoney(divisibility),
|
||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation),
|
||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
|
||||
|
@ -450,7 +445,7 @@ namespace BTCPayServer.Controllers
|
|||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
ItemDesc = invoice.Metadata.ItemDesc,
|
||||
Rate = ExchangeRate(paymentMethod),
|
||||
MerchantRefLink = invoice.RedirectURL?.AbsoluteUri ?? "/",
|
||||
RedirectAutomatically = invoice.RedirectAutomatically,
|
||||
|
@ -500,7 +495,7 @@ namespace BTCPayServer.Controllers
|
|||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob);
|
||||
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.PaymentMethodId = paymentMethodId.ToString();
|
||||
|
@ -509,17 +504,17 @@ namespace BTCPayServer.Controllers
|
|||
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 (cryptoCode == productInformation.Currency)
|
||||
if (cryptoCode == invoiceEntity.Currency)
|
||||
return null;
|
||||
|
||||
return _CurrencyNameTable.DisplayFormatCurrency(productInformation.Price, productInformation.Currency);
|
||||
return _CurrencyNameTable.DisplayFormatCurrency(invoiceEntity.Price, invoiceEntity.Currency);
|
||||
}
|
||||
private string ExchangeRate(PaymentMethod paymentMethod)
|
||||
{
|
||||
string currency = paymentMethod.ParentEntity.ProductInformation.Currency;
|
||||
string currency = paymentMethod.ParentEntity.Currency;
|
||||
return _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate, currency);
|
||||
}
|
||||
|
||||
|
@ -628,9 +623,9 @@ namespace BTCPayServer.Controllers
|
|||
ShowCheckout = invoice.Status == InvoiceStatus.New,
|
||||
Date = invoice.InvoiceTime,
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.OrderId ?? string.Empty,
|
||||
OrderId = invoice.Metadata.OrderId ?? 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(),
|
||||
CanMarkComplete = state.CanMarkComplete(),
|
||||
Details = InvoicePopulatePayments(invoice),
|
||||
|
@ -732,7 +727,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
try
|
||||
{
|
||||
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
var result = await CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||
{
|
||||
Price = model.Amount.Value,
|
||||
Currency = model.Currency,
|
||||
|
|
|
@ -23,9 +23,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using BuyerInformation = BTCPayServer.Services.Invoices.BuyerInformation;
|
||||
using CreateInvoiceRequest = BTCPayServer.Models.CreateInvoiceRequest;
|
||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
||||
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
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,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
@ -83,7 +81,7 @@ namespace BTCPayServer.Controllers
|
|||
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";
|
||||
InvoiceLogs logs = new InvoiceLogs();
|
||||
|
@ -99,23 +97,21 @@ namespace BTCPayServer.Controllers
|
|||
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
||||
}
|
||||
entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
|
||||
entity.OrderId = invoice.OrderId;
|
||||
entity.Metadata.OrderId = invoice.OrderId;
|
||||
entity.ServerUrl = serverUrl;
|
||||
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
||||
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
||||
entity.NotificationURLTemplate = invoice.NotificationURL;
|
||||
entity.NotificationEmail = invoice.NotificationEmail;
|
||||
entity.BuyerInformation = Map<CreateInvoiceRequest, BuyerInformation>(invoice);
|
||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
if (additionalTags != null)
|
||||
entity.InternalTags.AddRange(additionalTags);
|
||||
//Another way of passing buyer info to support
|
||||
FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
|
||||
if (entity?.BuyerInformation?.BuyerEmail != null)
|
||||
FillBuyerInfo(invoice, entity);
|
||||
if (entity.Metadata.BuyerEmail != null)
|
||||
{
|
||||
if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
|
||||
if (!EmailValidator.IsEmail(entity.Metadata.BuyerEmail))
|
||||
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;
|
||||
|
@ -132,8 +128,23 @@ namespace BTCPayServer.Controllers
|
|||
invoice.TaxIncluded = Math.Max(0.0m, taxIncluded);
|
||||
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;
|
||||
|
||||
|
@ -220,8 +231,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
entity.SetSupportedPaymentMethods(supported);
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
entity.PosData = invoice.PosData;
|
||||
|
||||
entity.Metadata.PosData = invoice.PosData;
|
||||
foreach (var app in await getAppsTaggingStore)
|
||||
{
|
||||
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
||||
|
@ -273,7 +283,7 @@ namespace BTCPayServer.Controllers
|
|||
var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
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)
|
||||
{
|
||||
return null;
|
||||
|
@ -337,24 +347,30 @@ namespace BTCPayServer.Controllers
|
|||
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)
|
||||
return;
|
||||
buyerInformation.BuyerAddress1 = buyerInformation.BuyerAddress1 ?? buyer.Address1;
|
||||
buyerInformation.BuyerAddress2 = buyerInformation.BuyerAddress2 ?? buyer.Address2;
|
||||
buyerInformation.BuyerCity = buyerInformation.BuyerCity ?? buyer.City;
|
||||
buyerInformation.BuyerCountry = buyerInformation.BuyerCountry ?? buyer.country;
|
||||
buyerInformation.BuyerEmail = buyerInformation.BuyerEmail ?? buyer.email;
|
||||
buyerInformation.BuyerName = buyerInformation.BuyerName ?? buyer.Name;
|
||||
buyerInformation.BuyerPhone = buyerInformation.BuyerPhone ?? buyer.phone;
|
||||
buyerInformation.BuyerState = buyerInformation.BuyerState ?? buyer.State;
|
||||
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
||||
}
|
||||
|
||||
private TDest Map<TFrom, TDest>(TFrom data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
||||
buyerInformation.BuyerAddress1 ??= buyer.Address1;
|
||||
buyerInformation.BuyerAddress2 ??= buyer.Address2;
|
||||
buyerInformation.BuyerCity ??= buyer.City;
|
||||
buyerInformation.BuyerCountry ??= buyer.country;
|
||||
buyerInformation.BuyerEmail ??= buyer.email;
|
||||
buyerInformation.BuyerName ??= buyer.Name;
|
||||
buyerInformation.BuyerPhone ??= buyer.phone;
|
||||
buyerInformation.BuyerState ??= buyer.State;
|
||||
buyerInformation.BuyerZip ??= buyer.zip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using CreateInvoiceRequest = BTCPayServer.Models.CreateInvoiceRequest;
|
||||
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
|
@ -260,7 +260,7 @@ namespace BTCPayServer.Controllers
|
|||
try
|
||||
{
|
||||
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)}",
|
||||
Currency = blob.Currency,
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers
|
|||
DataWrapper<InvoiceResponse> invoice = null;
|
||||
try
|
||||
{
|
||||
invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
||||
invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||
{
|
||||
Price = model.Price,
|
||||
Currency = model.Currency,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
@ -8,6 +9,17 @@ namespace BTCPayServer.Data
|
|||
{
|
||||
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(invoiceData.Blob), null);
|
||||
entity.Networks = networks;
|
||||
if (entity.Metadata is null)
|
||||
{
|
||||
if (entity.Version < InvoiceEntity.GreenfieldInvoices_Version)
|
||||
{
|
||||
entity.MigrateLegacyInvoice();
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Metadata = new InvoiceMetadata();
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
public static InvoiceState GetInvoiceState(this InvoiceData invoiceData)
|
||||
|
|
|
@ -104,9 +104,8 @@ namespace BTCPayServer.HostedServices
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
if ((!string.IsNullOrEmpty(invoiceEvent.Invoice.ProductInformation.ItemCode) ||
|
||||
AppService.TryParsePosCartItems(invoiceEvent.Invoice.PosData, out cartItems)))
|
||||
if ((!string.IsNullOrEmpty(invoiceEvent.Invoice.Metadata.ItemCode) ||
|
||||
AppService.TryParsePosCartItems(invoiceEvent.Invoice.Metadata.PosData, out cartItems)))
|
||||
{
|
||||
var appIds = AppService.GetAppInternalTags(invoiceEvent.Invoice);
|
||||
|
||||
|
@ -116,9 +115,9 @@ namespace BTCPayServer.HostedServices
|
|||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -2,10 +2,11 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class CreateInvoiceRequest
|
||||
public class BitpayCreateInvoiceRequest
|
||||
{
|
||||
[JsonProperty(PropertyName = "buyer")]
|
||||
public Buyer Buyer { get; set; }
|
||||
|
@ -81,5 +82,6 @@ namespace BTCPayServer.Models
|
|||
//Bitpay compatibility: create invoice in btcpay uses this instead of supportedTransactionCurrencies
|
||||
[JsonProperty(PropertyName = "paymentCurrencies", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public IEnumerable<string> PaymentCurrencies { get; set; }
|
||||
public JObject Metadata { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@ using BTCPayServer.Payments.Bitcoin;
|
|||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using BuyerInformation = BTCPayServer.Services.Invoices.BuyerInformation;
|
||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
|
@ -76,22 +74,12 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string RefundEmail
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string TaxIncluded { get; set; }
|
||||
public BuyerInformation BuyerInformation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string TransactionSpeed { get; set; }
|
||||
public object StoreName
|
||||
|
@ -116,11 +104,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
get;
|
||||
set;
|
||||
}
|
||||
public ProductInformation ProductInformation
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public InvoiceMetadata TypedMetadata { get; set; }
|
||||
public AddressModel[] Addresses { get; set; }
|
||||
public DateTimeOffset MonitoringDate { 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()
|
||||
{
|
||||
Id = entity.Id,
|
||||
Amount = entity.ProductInformation.Price,
|
||||
AmountFormatted = _currencies.FormatCurrency(entity.ProductInformation.Price, blob.Currency),
|
||||
Currency = entity.ProductInformation.Currency,
|
||||
Amount = entity.Price,
|
||||
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
|
||||
Currency = entity.Currency,
|
||||
ExpiryDate = entity.ExpirationTime.DateTime,
|
||||
Status = entity.GetInvoiceState().ToString(),
|
||||
Payments = entity
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
var storeBlob = store.GetStoreBlob();
|
||||
var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network);
|
||||
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 expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||
if (expiry < TimeSpan.Zero)
|
||||
|
@ -58,8 +58,8 @@ namespace BTCPayServer.Payments.Lightning
|
|||
|
||||
string description = storeBlob.LightningDescriptionTemplate;
|
||||
description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
.Replace("{ItemDescription}", invoice.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{OrderId}", invoice.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT))
|
||||
{
|
||||
try
|
||||
|
|
|
@ -97,8 +97,8 @@ namespace BTCPayServer.Services.Apps
|
|||
var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
||||
|
||||
var perkCount = paidInvoices
|
||||
.Where(entity => !string.IsNullOrEmpty(entity.ProductInformation.ItemCode))
|
||||
.GroupBy(entity => entity.ProductInformation.ItemCode)
|
||||
.Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
||||
|
||||
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||
|
@ -331,12 +331,12 @@ namespace BTCPayServer.Services.Apps
|
|||
public Contributions GetContributionsByPaymentMethodId(string currency, InvoiceEntity[] invoices, bool softcap)
|
||||
{
|
||||
var contributions = invoices
|
||||
.Where(p => p.ProductInformation.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(p => p.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(p =>
|
||||
{
|
||||
var contribution = new Contribution();
|
||||
contribution.PaymentMethodId = new PaymentMethodId(p.ProductInformation.Currency, PaymentTypes.BTCLike);
|
||||
contribution.CurrencyValue = p.ProductInformation.Price;
|
||||
contribution.PaymentMethodId = new PaymentMethodId(p.Currency, PaymentTypes.BTCLike);
|
||||
contribution.CurrencyValue = p.Price;
|
||||
contribution.Value = contribution.CurrencyValue;
|
||||
|
||||
// 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)
|
||||
{
|
||||
var exportList = new List<ExportInvoiceHolder>();
|
||||
var currency = Currencies.GetNumberFormatInfo(invoice.ProductInformation.Currency, true);
|
||||
|
||||
var invoiceDue = invoice.ProductInformation.Price;
|
||||
var currency = Currencies.GetNumberFormatInfo(invoice.Currency, true);
|
||||
var invoiceDue = invoice.Price;
|
||||
// in this first version we are only exporting invoices that were paid
|
||||
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"
|
||||
NetworkFee = payment.NetworkFee.ToString(CultureInfo.InvariantCulture),
|
||||
InvoiceDue = Math.Round(invoiceDue, currency.NumberDecimalDigits),
|
||||
OrderId = invoice.OrderId,
|
||||
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||
StoreId = invoice.StoreId,
|
||||
InvoiceId = invoice.Id,
|
||||
InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||
|
@ -99,11 +98,11 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||
InvoiceStatus = invoice.StatusString,
|
||||
InvoiceExceptionStatus = invoice.ExceptionStatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
InvoiceItemCode = invoice.ProductInformation.ItemCode,
|
||||
InvoiceItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
InvoicePrice = invoice.ProductInformation.Price,
|
||||
InvoiceCurrency = invoice.ProductInformation.Currency,
|
||||
BuyerEmail = invoice.BuyerInformation?.BuyerEmail
|
||||
InvoiceItemCode = invoice.Metadata.ItemCode,
|
||||
InvoiceItemDesc = invoice.Metadata.ItemDesc,
|
||||
InvoicePrice = invoice.Price,
|
||||
InvoiceCurrency = invoice.Currency,
|
||||
BuyerEmail = invoice.Metadata.BuyerEmail
|
||||
};
|
||||
|
||||
exportList.Add(target);
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using Amazon.Runtime.Internal.Util;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.JsonConverters;
|
||||
|
@ -9,17 +10,31 @@ using BTCPayServer.Models;
|
|||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
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")]
|
||||
public string BuyerName
|
||||
{
|
||||
|
@ -66,10 +81,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductInformation
|
||||
{
|
||||
[JsonProperty(PropertyName = "itemDesc")]
|
||||
public string ItemDesc
|
||||
{
|
||||
|
@ -81,35 +93,122 @@ namespace BTCPayServer.Services.Invoices
|
|||
get; set;
|
||||
}
|
||||
[JsonProperty(PropertyName = "physical")]
|
||||
public bool Physical
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "price")]
|
||||
public decimal Price
|
||||
public bool? Physical
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public decimal TaxIncluded
|
||||
public decimal? TaxIncluded
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string PosData { get; set; }
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "currency")]
|
||||
public string Currency
|
||||
public static InvoiceMetadata FromJObject(JObject jObject)
|
||||
{
|
||||
get; set;
|
||||
return jObject.ToObject<InvoiceMetadata>(MetadataSerializer);
|
||||
}
|
||||
public JObject ToJObject()
|
||||
{
|
||||
return JObject.FromObject(this, MetadataSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
public BTCPayNetworkProvider Networks { get; set; }
|
||||
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 string Id
|
||||
{
|
||||
|
@ -120,11 +219,6 @@ namespace BTCPayServer.Services.Invoices
|
|||
get; set;
|
||||
}
|
||||
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
|
@ -148,20 +242,20 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
public ProductInformation ProductInformation
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public BuyerInformation BuyerInformation
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string PosData
|
||||
|
||||
public InvoiceMetadata Metadata
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public decimal Price { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public HashSet<string> InternalTags { get; set; } = new HashSet<string>();
|
||||
|
||||
|
@ -300,7 +394,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
|
||||
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);
|
||||
if (Uri.TryCreate(uriStr, UriKind.Absolute, out var uri) && (uri.Scheme == "http" || uri.Scheme == "https"))
|
||||
return uri;
|
||||
|
@ -383,8 +477,8 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
Id = Id,
|
||||
StoreId = StoreId,
|
||||
OrderId = OrderId,
|
||||
PosData = PosData,
|
||||
OrderId = Metadata.OrderId,
|
||||
PosData = Metadata.PosData,
|
||||
CurrentTime = DateTimeOffset.UtcNow,
|
||||
InvoiceTime = InvoiceTime,
|
||||
ExpirationTime = ExpirationTime,
|
||||
|
@ -392,7 +486,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
Status = StatusString,
|
||||
ExceptionStatus = ExceptionStatus == InvoiceExceptionStatus.None ? new JValue(false) : new JValue(ExceptionStatusString),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Currency = ProductInformation.Currency,
|
||||
Currency = Currency,
|
||||
Flags = new Flags() { Refundable = Refundable },
|
||||
PaymentSubtotals = new Dictionary<string, decimal>(),
|
||||
PaymentTotals = new Dictionary<string, decimal>(),
|
||||
|
@ -415,7 +509,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
var address = details?.GetPaymentDestination();
|
||||
var exrates = new Dictionary<string, decimal>
|
||||
{
|
||||
{ ProductInformation.Currency, cryptoInfo.Rate }
|
||||
{ Currency, cryptoInfo.Rate }
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
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.Add(new JProperty("name", BuyerInformation.BuyerName));
|
||||
dto.Buyer.Add(new JProperty("address1", BuyerInformation.BuyerAddress1));
|
||||
dto.Buyer.Add(new JProperty("address2", BuyerInformation.BuyerAddress2));
|
||||
dto.Buyer.Add(new JProperty("locality", BuyerInformation.BuyerCity));
|
||||
dto.Buyer.Add(new JProperty("region", BuyerInformation.BuyerState));
|
||||
dto.Buyer.Add(new JProperty("postalCode", BuyerInformation.BuyerZip));
|
||||
dto.Buyer.Add(new JProperty("country", BuyerInformation.BuyerCountry));
|
||||
dto.Buyer.Add(new JProperty("phone", BuyerInformation.BuyerPhone));
|
||||
dto.Buyer.Add(new JProperty("email", string.IsNullOrWhiteSpace(BuyerInformation.BuyerEmail) ? RefundMail : BuyerInformation.BuyerEmail));
|
||||
dto.Buyer.Add(new JProperty("name", Metadata.BuyerName));
|
||||
dto.Buyer.Add(new JProperty("address1", Metadata.BuyerAddress1));
|
||||
dto.Buyer.Add(new JProperty("address2", Metadata.BuyerAddress2));
|
||||
dto.Buyer.Add(new JProperty("locality", Metadata.BuyerCity));
|
||||
dto.Buyer.Add(new JProperty("region", Metadata.BuyerState));
|
||||
dto.Buyer.Add(new JProperty("postalCode", Metadata.BuyerZip));
|
||||
dto.Buyer.Add(new JProperty("country", Metadata.BuyerCountry));
|
||||
dto.Buyer.Add(new JProperty("phone", Metadata.BuyerPhone));
|
||||
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.Guid = Guid.NewGuid().ToString();
|
||||
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)
|
||||
{
|
||||
var rates = GetPaymentMethods();
|
||||
|
@ -602,6 +694,62 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
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);
|
||||
var paymentMethods = ParentEntity.GetPaymentMethods();
|
||||
|
||||
var totalDue = ParentEntity.ProductInformation.Price / Rate;
|
||||
var totalDue = ParentEntity.Price / Rate;
|
||||
var paid = 0m;
|
||||
var cryptoPaid = 0.0m;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Encoders = NBitcoin.DataEncoders.Encoders;
|
||||
using InvoiceData = BTCPayServer.Data.InvoiceData;
|
||||
|
||||
|
@ -65,6 +66,7 @@ retry:
|
|||
Networks = _Networks,
|
||||
Version = InvoiceEntity.Lastest_Version,
|
||||
InvoiceTime = DateTimeOffset.UtcNow,
|
||||
Metadata = new InvoiceMetadata()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -162,11 +164,11 @@ retry:
|
|||
Id = invoice.Id,
|
||||
Created = invoice.InvoiceTime,
|
||||
Blob = ToBytes(invoice, null),
|
||||
OrderId = invoice.OrderId,
|
||||
OrderId = invoice.Metadata.OrderId,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
ItemCode = invoice.ProductInformation.ItemCode,
|
||||
ItemCode = invoice.Metadata.ItemCode,
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
Archived = false
|
||||
});
|
||||
|
@ -198,10 +200,9 @@ retry:
|
|||
|
||||
textSearch.Add(invoice.Id);
|
||||
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.OrderId);
|
||||
textSearch.Add(ToString(invoice.BuyerInformation, null));
|
||||
textSearch.Add(ToString(invoice.ProductInformation, null));
|
||||
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.Metadata.OrderId);
|
||||
textSearch.Add(ToString(invoice.Metadata, null));
|
||||
textSearch.Add(invoice.StoreId);
|
||||
|
||||
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
||||
|
@ -555,10 +556,9 @@ retry:
|
|||
{
|
||||
entity.Events = invoice.Events.OrderBy(c => c.Timestamp).ToList();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(entity.RefundMail) && string.IsNullOrEmpty(entity.BuyerInformation.BuyerEmail))
|
||||
if (!string.IsNullOrEmpty(entity.RefundMail) && string.IsNullOrEmpty(entity.Metadata.BuyerEmail))
|
||||
{
|
||||
entity.BuyerInformation.BuyerEmail = entity.RefundMail;
|
||||
entity.Metadata.BuyerEmail = entity.RefundMail;
|
||||
}
|
||||
entity.Archived = invoice.Archived;
|
||||
return entity;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@model InvoiceDetailsModel
|
||||
@model InvoiceDetailsModel
|
||||
@{
|
||||
ViewData["Title"] = "Invoice " + Model.Id;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th>Order Id</th>
|
||||
<td>@Model.OrderId</td>
|
||||
<td>@Model.TypedMetadata.OrderId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total fiat due</th>
|
||||
|
@ -109,39 +109,39 @@
|
|||
<table class="table table-sm table-responsive-md removetopborder">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>@Model.BuyerInformation.BuyerName</td>
|
||||
<td>@Model.TypedMetadata.BuyerName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<th>Phone</th>
|
||||
<td>@Model.BuyerInformation.BuyerPhone</td>
|
||||
<td>@Model.TypedMetadata.BuyerPhone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 1</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress1</td>
|
||||
<td>@Model.TypedMetadata.BuyerAddress1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 2</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress2</td>
|
||||
<td>@Model.TypedMetadata.BuyerAddress2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<td>@Model.BuyerInformation.BuyerCity</td>
|
||||
<td>@Model.TypedMetadata.BuyerCity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>@Model.BuyerInformation.BuyerState</td>
|
||||
<td>@Model.TypedMetadata.BuyerState</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Country</th>
|
||||
<td>@Model.BuyerInformation.BuyerCountry</td>
|
||||
<td>@Model.TypedMetadata.BuyerCountry</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zip</th>
|
||||
<td>@Model.BuyerInformation.BuyerZip</td>
|
||||
<td>@Model.TypedMetadata.BuyerZip</td>
|
||||
</tr>
|
||||
</table>
|
||||
@if (Model.PosData.Count == 0)
|
||||
|
@ -150,11 +150,11 @@
|
|||
<table class="table table-sm table-responsive-md removetopborder">
|
||||
<tr>
|
||||
<th>Item code</th>
|
||||
<td>@Model.ProductInformation.ItemCode</td>
|
||||
<td>@Model.TypedMetadata.ItemCode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Item Description</th>
|
||||
<td>@Model.ProductInformation.ItemDesc</td>
|
||||
<td>@Model.TypedMetadata.ItemDesc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
|
@ -177,11 +177,11 @@
|
|||
<table class="table table-sm table-responsive-md removetopborder">
|
||||
<tr>
|
||||
<th>Item code</th>
|
||||
<td>@Model.ProductInformation.ItemCode</td>
|
||||
<td>@Model.TypedMetadata.ItemCode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Item Description</th>
|
||||
<td>@Model.ProductInformation.ItemDesc</td>
|
||||
<td>@Model.TypedMetadata.ItemDesc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
|
|
Loading…
Add table
Reference in a new issue