From 8962bf00f61f2a6fe488ce986ef529d08ba5719a Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 10 Sep 2020 13:54:36 +0200 Subject: [PATCH 1/6] GreenField: Add Invoice Payment methods endpoints lets you fetch all active payment methods data + payments made --- .../BTCPayServerClient.Invoices.cs | 7 + BTCPayServer.Client/Models/InvoiceData.cs | 2 - .../Models/InvoicePaymentMethodDataModel.cs | 64 +++++ BTCPayServer.Tests/GreenfieldAPITests.cs | 4 +- .../GreenField/InvoiceController.cs | 66 ++++++ .../swagger/v1/swagger.template.invoices.json | 223 ++++++++++++++++++ 6 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs diff --git a/BTCPayServer.Client/BTCPayServerClient.Invoices.cs b/BTCPayServer.Client/BTCPayServerClient.Invoices.cs index 7710ea118..8a34fffec 100644 --- a/BTCPayServer.Client/BTCPayServerClient.Invoices.cs +++ b/BTCPayServer.Client/BTCPayServerClient.Invoices.cs @@ -26,6 +26,13 @@ namespace BTCPayServer.Client CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}"), token); return await HandleResponse(response); } + public virtual async Task GetInvoicePaymentMethods(string storeId, string invoiceId, + CancellationToken token = default) + { + var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods"), token); + return await HandleResponse(response); + } public virtual async Task ArchiveInvoice(string storeId, string invoiceId, CancellationToken token = default) diff --git a/BTCPayServer.Client/Models/InvoiceData.cs b/BTCPayServer.Client/Models/InvoiceData.cs index f31644f94..63f391182 100644 --- a/BTCPayServer.Client/Models/InvoiceData.cs +++ b/BTCPayServer.Client/Models/InvoiceData.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using BTCPayServer.JsonConverters; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs new file mode 100644 index 000000000..29b8da013 --- /dev/null +++ b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using BTCPayServer.JsonConverters; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace BTCPayServer.Client.Models +{ + public class InvoicePaymentMethodDataModel + { + public string Destination { get; set; } + public string PaymentLink { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal Rate { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal PaymentMethodPaid { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal TotalPaid { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal Due { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal Amount { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal NetworkFee { get; set; } + + public List Payments { get; set; } + public string PaymentMethod { get; set; } + + public class Payment + { + public string Id { get; set; } + + [JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))] + public DateTime ReceivedDate { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal Value { get; set; } + + [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + public decimal Fee { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public PaymentStatus Status { get; set; } + + public string Destination { get; set; } + + + + public enum PaymentStatus + { + Invalid, + AwaitingConfirmation, + AwaitingCompletion, + Complete + } + } + } +} diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 62f73fab6..e56dc74b2 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -848,9 +848,11 @@ namespace BTCPayServer.Tests Assert.Single(invoices); Assert.Equal(newInvoice.Id, invoices.First().Id); - //get payment request + //get var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id); Assert.Equal(newInvoice.Metadata, invoice.Metadata); + var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id); + //update invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id); diff --git a/BTCPayServer/Controllers/GreenField/InvoiceController.cs b/BTCPayServer/Controllers/GreenField/InvoiceController.cs index c80b4fe97..376307882 100644 --- a/BTCPayServer/Controllers/GreenField/InvoiceController.cs +++ b/BTCPayServer/Controllers/GreenField/InvoiceController.cs @@ -212,8 +212,74 @@ namespace BTCPayServer.Controllers.GreenField await _invoiceRepository.ToggleInvoiceArchival(invoiceId, false, storeId); return await GetInvoice(storeId, invoiceId); } + + [Authorize(Policy = Policies.CanViewInvoices, + AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + [HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")] + public async Task GetInvoicePaymentMethods(string storeId, string invoiceId) + { + var store = HttpContext.GetStoreData(); + if (store == null) + { + return NotFound(); + } + var invoice = await _invoiceRepository.GetInvoice(invoiceId, true); + if (invoice.StoreId != store.Id) + { + return NotFound(); + } + return Ok(ToPaymentMethodModels(invoice)); + } + + private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity) + { + return entity.GetPaymentMethods().Select( + method => + { + var accounting = method.Calculate(); + var details = method.GetPaymentMethodDetails(); + var payments = method.ParentEntity.GetPayments().Where(paymentEntity => + paymentEntity.GetPaymentMethodId() == method.GetId()); + + return new InvoicePaymentMethodDataModel() + { + PaymentMethod = method.GetId().ToStringNormalized(), + Destination = details.GetPaymentDestination(), + Rate = method.Rate, + Due = accounting.Due.ToDecimal(MoneyUnit.BTC), + TotalPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC), + PaymentMethodPaid = accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), + Amount = accounting.Due.ToDecimal(MoneyUnit.BTC), + NetworkFee = accounting.NetworkFee.ToDecimal(MoneyUnit.BTC), + PaymentLink = + method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due, + Request.GetAbsoluteRoot()), + Payments = payments.Select(paymentEntity => + { + var data = paymentEntity.GetCryptoPaymentData(); + return new InvoicePaymentMethodDataModel.Payment() + { + Destination = data.GetDestination(), + Id = data.GetPaymentId(), + Status = !paymentEntity.Accounted + ? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid + : data.PaymentCompleted(paymentEntity) + ? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Complete + : data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) + ? InvoicePaymentMethodDataModel.Payment.PaymentStatus + .AwaitingCompletion + : InvoicePaymentMethodDataModel.Payment.PaymentStatus + .AwaitingConfirmation, + Fee = paymentEntity.NetworkFee, + Value = data.GetValue(), + ReceivedDate = paymentEntity.ReceivedTime.DateTime + }; + }).ToList() + }; + }).ToArray(); + } private InvoiceData ToModel(InvoiceEntity entity) { return new InvoiceData() diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json index aee6d8cf9..14d40a21d 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json @@ -216,6 +216,123 @@ ] } }, + "/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods": { + "get": { + "tags": [ + "Invoices" + ], + "summary": "Get invoice payment methods", + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store to fetch", + "schema": { + "type": "string" + } + }, + { + "name": "invoiceId", + "in": "path", + "required": true, + "description": "The invoice to fetch", + "schema": { + "type": "string" + } + } + ], + "description": "View information about the specified invoice's payment methods", + "operationId": "Invoices_GetInvoicePaymentMethods", + "responses": { + "200": { + "description": "specified invoice payment methods data", + "content": { + "application/json": { + "schema": { + "type": "array", + "nullable": false, + "items": { + "$ref": "#/components/schemas/InvoicePaymentMethodDataModel" + } + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to view the specified invoie" + }, + "404": { + "description": "The key is not found for this invoice" + } + }, + "security": [ + { + "API Key": [ + "btcpay.store.canviewinvoices" + ], + "Basic": [] + } + ] + }, + "delete": { + "tags": [ + "Invoices" + ], + "summary": "Archive invoice", + "description": "Archives the specified invoice.", + "operationId": "Invoices_ArchiveInvoice", + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store the invoice belongs to", + "schema": { + "type": "string" + } + }, + { + "name": "invoiceId", + "in": "path", + "required": true, + "description": "The invoice to remove", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The invoice has been archived" + }, + "400": { + "description": "A list of errors that occurred when archiving the invoice", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationProblemDetails" + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to archive the specified invoice" + }, + "404": { + "description": "The key is not found for this invoice" + } + }, + "security": [ + { + "API Key": [ + "btcpay.store.canmodifystoresettings" + ], + "Basic": [] + } + ] + } + }, "/api/v1/stores/{storeId}/invoices/{invoiceId}/status": { "post": { "tags": [ @@ -565,6 +682,112 @@ "LowSpeed", "LowMediumSpeed" ] + }, + "InvoicePaymentMethodDataModel": { + "type": "object", + "additionalProperties": false, + "properties": { + "paymentMethod": { + "type": "string", + "description": "The payment method" + }, + "destination": { + "type": "string", + "description": "The destination the payment must be made to" + }, + "paymentLink": { + "type": "string", + "nullable": true, + "description": "A payment link that helps pay to the payment destination" + }, + "rate": { + "type": "string", + "format": "decimal", + "description": "The rate between this payment method's currency and the invoice currency" + }, + "paymentMethodPaid": { + "type": "string", + "format": "decimal", + "description": "The amount paid by this payment method" + }, + "totalPaid": { + "type": "string", + "format": "decimal", + "description": "The total amount paid by all payment methods to the invoice, converted to this payment method's currency" + }, + "due": { + "type": "string", + "format": "decimal", + "description": "The total amount left to be paid, converted to this payment method's currency" + }, + "amount": { + "type": "string", + "format": "decimal", + "description": "The invoice amount, converted to this payment method's currency" + }, + "networkFee": { + "type": "string", + "format": "decimal", + "description": "The added merchant fee to pay for network costs of this payment method." + }, + "payments": { + "type": "array", + "nullable": false, + "items": { + "$ref": "#/components/schemas/Payment" + }, + "description": "Payments made with this payment method." + } + } + }, + "Payment": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "A unique identifier for this payment" + }, + "receivedDate": { + "type": "string", + "format": "date-time", + "description": "The date the payment was recorded" + }, + "value": { + "type": "string", + "format": "decimal", + "description": "The value of the payment" + }, + "fee": { + "type": "string", + "format": "decimal", + "description": "The fee paid for the payment" + }, + "status": { + "$ref": "#/components/schemas/PaymentStatus", + "description": "The status of the payment" + }, + "destination": { + "type": "string", + "description": "The destination the payment was made to" + } + } + }, + "PaymentStatus": { + "type": "string", + "description": "", + "x-enumNames": [ + "Invalid", + "AwaitingConfirmation", + "AwaitingCompletion", + "Complete" + ], + "enum": [ + "Invalid", + "AwaitingConfirmation", + "AwaitingCompletion", + "Complete" + ] } } }, From 9a92bc05dbccaaf1974f7f07d576c177f10d8760 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 23 Oct 2020 10:40:08 +0200 Subject: [PATCH 2/6] fix json types --- .../Models/InvoicePaymentMethodDataModel.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs index 29b8da013..ab4e8ae40 100644 --- a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs +++ b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs @@ -11,22 +11,22 @@ namespace BTCPayServer.Client.Models public string Destination { get; set; } public string PaymentLink { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(StringEnumConverter))] public decimal Rate { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal PaymentMethodPaid { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal TotalPaid { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal Due { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal Amount { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal NetworkFee { get; set; } public List Payments { get; set; } @@ -39,19 +39,17 @@ namespace BTCPayServer.Client.Models [JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))] public DateTime ReceivedDate { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal Value { get; set; } - [JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal Fee { get; set; } [JsonConverter(typeof(StringEnumConverter))] public PaymentStatus Status { get; set; } public string Destination { get; set; } - - - + public enum PaymentStatus { Invalid, From 40d95acfb9656cbf76c77bae8a9ad3693999341e Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Fri, 23 Oct 2020 10:41:12 +0200 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Dennis Reimann --- BTCPayServer.Tests/GreenfieldAPITests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index e56dc74b2..0efd55c6a 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -852,6 +852,10 @@ namespace BTCPayServer.Tests var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id); Assert.Equal(newInvoice.Metadata, invoice.Metadata); var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id); + Assert.Equal(1, paymentMethods.Length); + var paymentMethod = paymentMethods.First(); + Assert.Equal("BTC", paymentMethod.PaymentMethod); + Assert.Equal(0, paymentMethod.Payments.Count); //update From abc9d0797751e8ca269bb48ae2fcd77c1b2a259a Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 23 Oct 2020 10:48:08 +0200 Subject: [PATCH 4/6] fix swagger format for dates --- .../wwwroot/swagger/v1/swagger.template.invoices.json | 2 +- .../wwwroot/swagger/v1/swagger.template.payment-requests.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json index 14d40a21d..97441f56c 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json @@ -750,7 +750,7 @@ }, "receivedDate": { "type": "string", - "format": "date-time", + "format": "int64", "description": "The date the payment was recorded" }, "value": { diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.payment-requests.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.payment-requests.json index 9bbde2c4b..90dc62bee 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.payment-requests.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.payment-requests.json @@ -299,7 +299,7 @@ "type": "string", "description": "The creation date of the payment request", "nullable": false, - "format": "date-time" + "format": "int64" } } }, @@ -342,7 +342,7 @@ "type": "string", "description": "The expiry date of the payment request", "nullable": true, - "format": "date-time" + "format": "int64" }, "embeddedCSS": { "type": "string", From 1d82c3779b4701342a17abe2b94a7f16710e3b48 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 27 Oct 2020 09:49:35 +0100 Subject: [PATCH 5/6] Remove AwaitingConfirmation --- .../Models/InvoicePaymentMethodDataModel.cs | 1 - BTCPayServer/Controllers/GreenField/InvoiceController.cs | 9 +++------ .../wwwroot/swagger/v1/swagger.template.invoices.json | 2 -- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs index ab4e8ae40..609e7ddb3 100644 --- a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs +++ b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs @@ -53,7 +53,6 @@ namespace BTCPayServer.Client.Models public enum PaymentStatus { Invalid, - AwaitingConfirmation, AwaitingCompletion, Complete } diff --git a/BTCPayServer/Controllers/GreenField/InvoiceController.cs b/BTCPayServer/Controllers/GreenField/InvoiceController.cs index 376307882..86393594c 100644 --- a/BTCPayServer/Controllers/GreenField/InvoiceController.cs +++ b/BTCPayServer/Controllers/GreenField/InvoiceController.cs @@ -265,13 +265,10 @@ namespace BTCPayServer.Controllers.GreenField Id = data.GetPaymentId(), Status = !paymentEntity.Accounted ? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid - : data.PaymentCompleted(paymentEntity) + : data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) || + data.PaymentCompleted(paymentEntity) ? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Complete - : data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) - ? InvoicePaymentMethodDataModel.Payment.PaymentStatus - .AwaitingCompletion - : InvoicePaymentMethodDataModel.Payment.PaymentStatus - .AwaitingConfirmation, + : InvoicePaymentMethodDataModel.Payment.PaymentStatus.AwaitingCompletion, Fee = paymentEntity.NetworkFee, Value = data.GetValue(), ReceivedDate = paymentEntity.ReceivedTime.DateTime diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json index 97441f56c..5da8a0230 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.invoices.json @@ -778,13 +778,11 @@ "description": "", "x-enumNames": [ "Invalid", - "AwaitingConfirmation", "AwaitingCompletion", "Complete" ], "enum": [ "Invalid", - "AwaitingConfirmation", "AwaitingCompletion", "Complete" ] From 256d711fde8ea87f19b81308707dcced734d0adb Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 27 Oct 2020 11:01:22 +0100 Subject: [PATCH 6/6] fix --- BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs index 609e7ddb3..afd59a652 100644 --- a/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs +++ b/BTCPayServer.Client/Models/InvoicePaymentMethodDataModel.cs @@ -11,7 +11,7 @@ namespace BTCPayServer.Client.Models public string Destination { get; set; } public string PaymentLink { get; set; } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(NumericStringJsonConverter))] public decimal Rate { get; set; } [JsonConverter(typeof(NumericStringJsonConverter))]