Merge pull request #1901 from btcpayserver/api/invoice-extras

GreenField: Add Invoice Payment methods endpoints
This commit is contained in:
Nicolas Dorier 2020-11-20 10:25:00 +09:00 committed by GitHub
commit f9b86a6b2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 361 additions and 5 deletions

View File

@ -26,6 +26,13 @@ namespace BTCPayServer.Client
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}"), token);
return await HandleResponse<InvoiceData>(response);
}
public virtual async Task<InvoicePaymentMethodDataModel[]> 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<InvoicePaymentMethodDataModel[]>(response);
}
public virtual async Task ArchiveInvoice(string storeId, string invoiceId,
CancellationToken token = default)

View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

View File

@ -0,0 +1,61 @@
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; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Rate { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal PaymentMethodPaid { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal TotalPaid { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Due { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal NetworkFee { get; set; }
public List<Payment> 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; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Value { get; set; }
[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,
AwaitingCompletion,
Complete
}
}
}
}

View File

@ -945,9 +945,15 @@ 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);
Assert.Equal(1, paymentMethods.Length);
var paymentMethod = paymentMethods.First();
Assert.Equal("BTC", paymentMethod.PaymentMethod);
Assert.Equal(0, paymentMethod.Payments.Count);
//update
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);

View File

@ -213,8 +213,71 @@ 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<IActionResult> 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.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) ||
data.PaymentCompleted(paymentEntity)
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Complete
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.AwaitingCompletion,
Fee = paymentEntity.NetworkFee,
Value = data.GetValue(),
ReceivedDate = paymentEntity.ReceivedTime.DateTime
};
}).ToList()
};
}).ToArray();
}
private InvoiceData ToModel(InvoiceEntity entity)
{
return new InvoiceData()

View File

@ -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,110 @@
"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": "int64",
"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",
"AwaitingCompletion",
"Complete"
],
"enum": [
"Invalid",
"AwaitingCompletion",
"Complete"
]
}
}
},

View File

@ -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",