Add callback doc

This commit is contained in:
nicolas.dorier
2020-11-16 12:05:15 +09:00
parent df79c2cf48
commit 07c5c2972d
8 changed files with 359 additions and 12 deletions

View File

@@ -54,12 +54,12 @@ namespace BTCPayServer.Client
return await HandleResponse<string>(response);
}
public async Task<JObject> GetWebhookDeliveryRequest(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
public async Task<WebhookEvent> GetWebhookDeliveryRequest(string storeId, string webhookId, string deliveryId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request"), token);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
return null;
return await HandleResponse<JObject>(response);
return await HandleResponse<WebhookEvent>(response);
}
}
}

View File

@@ -18,12 +18,19 @@ namespace BTCPayServer.Client.Models
DefaultSerializerSettings.Formatting = Formatting.None;
}
public string DeliveryId { get; set; }
public string WebhookId { get; set; }
public string OrignalDeliveryId { get; set; }
public bool IsRedelivery { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public WebhookEventType Type { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset Timestamp { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; }
public T ReadAs<T>()
{
var str = JsonConvert.SerializeObject(this, DefaultSerializerSettings);
return JsonConvert.DeserializeObject<T>(str, DefaultSerializerSettings);
}
}
}

View File

@@ -20,12 +20,6 @@ namespace BTCPayServer.Client.Models
public string StoreId { get; set; }
[JsonProperty(Order = 2)]
public string InvoiceId { get; set; }
public T ReadAs<T>()
{
var str = JsonConvert.SerializeObject(this, DefaultSerializerSettings);
return JsonConvert.DeserializeObject<T>(str, DefaultSerializerSettings);
}
}
public class WebhookInvoiceConfirmedEvent : WebhookInvoiceEvent

View File

@@ -16,6 +16,7 @@ using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.OpenAsset;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -670,6 +671,9 @@ namespace BTCPayServer.Tests
var newDelivery = await clientProfile.GetWebhookDelivery(user.StoreId, hook.Id, newDeliveryId);
Assert.NotNull(newDelivery);
Assert.Equal(404, newDelivery.HttpCode);
var req = await clientProfile.GetWebhookDeliveryRequest(user.StoreId, hook.Id, newDeliveryId);
Assert.Equal(delivery.Id, req.OrignalDeliveryId);
Assert.True(req.IsRedelivery);
Assert.Equal(WebhookDeliveryStatus.HttpError, newDelivery.Status);
});
deliveries = await clientProfile.GetWebhookDeliveries(user.StoreId, hook.Id);
@@ -995,12 +999,13 @@ namespace BTCPayServer.Tests
if (marked == InvoiceStatus.Invalid)
{
Assert.Equal(InvoiceStatus.Invalid, result.Status);
user.AssertHasWebhookEvent<WebhookInvoiceInvalidEvent>(WebhookEventType.InvoiceInvalid,
var evt = user.AssertHasWebhookEvent<WebhookInvoiceInvalidEvent>(WebhookEventType.InvoiceInvalid,
o =>
{
Assert.Equal(inv.Id, o.InvoiceId);
Assert.True(o.ManuallyMarked);
});
Assert.NotNull(await client.GetWebhookDelivery(evt.StoreId, evt.WebhookId, evt.DeliveryId));
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using BTCPayServer.Services.Invoices;
using Newtonsoft.Json.Linq;

View File

@@ -109,7 +109,10 @@ namespace BTCPayServer.HostedServices
newDeliveryBlob.Request = oldDeliveryBlob.Request;
var webhookEvent = newDeliveryBlob.ReadRequestAs<WebhookEvent>();
webhookEvent.DeliveryId = newDelivery.Id;
webhookEvent.WebhookId = webhookDelivery.Webhook.Id;
// if we redelivered a redelivery, we still want the initial delivery here
webhookEvent.OrignalDeliveryId ??= deliveryId;
webhookEvent.IsRedelivery = true;
newDeliveryBlob.Request = ToBytes(webhookEvent);
newDelivery.SetBlob(newDeliveryBlob);
return new WebhookDeliveryRequest(webhookDelivery.Webhook.Id, webhookEvent, newDelivery, webhookDelivery.Webhook.GetBlob());
@@ -131,7 +134,9 @@ namespace BTCPayServer.HostedServices
webhookEvent.InvoiceId = invoiceEvent.InvoiceId;
webhookEvent.StoreId = invoiceEvent.Invoice.StoreId;
webhookEvent.DeliveryId = delivery.Id;
webhookEvent.WebhookId = webhook.Id;
webhookEvent.OrignalDeliveryId = delivery.Id;
webhookEvent.IsRedelivery = false;
webhookEvent.Timestamp = delivery.Timestamp;
var context = new WebhookDeliveryRequest(webhook.Id, webhookEvent, delivery, webhookBlob);
EnqueueDelivery(context);

View File

@@ -1,4 +1,4 @@
@{
@{
Layout = null;
}
<!DOCTYPE html>
@@ -23,6 +23,6 @@
</head>
<body>
<redoc spec-url="@Url.ActionLink("Swagger")"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-rc.24/bundles/redoc.standalone.js" integrity="sha384-ZO+OTQZMsYIcoraCBa8iJW/5b2O8K1ujHmRfOwSvpVBlHUeKq5t3/kh1p8JQJ99X" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-rc.45/bundles/redoc.standalone.js" integrity="sha384-RC31+q3tyqdcilXYaU++ii/FAByqeZ+sjKUHMJ8hMzIY5k4kzNqi4Ett88EZ/4lq" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -534,7 +534,7 @@
"properties": {
"secret": {
"type": "string",
"description": "Must be used by the callback receiver to ensure the delivery comes from BTCPay Server. BTCPay Server includes the `BTCPay-Sig` HTTP header, whose format is `sha256=[HMAC256(secret,bodyBytes)]`. The pattern to authenticate the webhook is similar to [how to secure webhooks in Github](https://developer.github.com/webhooks/securing/) but with sha256 instead of sha1.",
"description": "Must be used by the callback receiver to ensure the delivery comes from BTCPay Server. BTCPay Server includes the `BTCPay-Sig` HTTP header, whose format is `sha256=HMAC256(UTF8(webhook's secret), body)`. The pattern to authenticate the webhook is similar to [how to secure webhooks in Github](https://developer.github.com/webhooks/securing/) but with sha256 instead of sha1.",
"nullable": false
}
}
@@ -585,6 +585,341 @@
}
}
}
},
"WebhookEvent": {
"type": "object",
"additionalProperties": false,
"properties": {
"deliveryId": {
"type": "string",
"nullable": false,
"description": "The delivery id of the webhook"
},
"webhookId": {
"type": "string",
"nullable": false,
"description": "The id of the webhook"
},
"originalDeliveryId": {
"type": "string",
"nullable": false,
"description": "If this delivery is a redelivery, the is the delivery id of the original delivery."
},
"isRedelivery": {
"type": "boolean",
"nullable": false,
"description": "True if this delivery is a redelivery"
},
"type": {
"type": "string",
"nullable": false,
"description": "The type of this event, current available are `InvoiceCreated`, `InvoiceReceivedPayment`, `InvoicePaidInFull`, `InvoiceExpired`, `InvoiceConfirmed`, and `InvoiceInvalid`."
},
"timestamp": {
"type": "number",
"format": "int64",
"description": "The timestamp when this delivery has been created"
}
}
},
"WebhookInvoiceEvent": {
"allOf": [
{
"$ref": "#/components/schemas/WebhookEvent"
},
{
"type": "object",
"properties": {
"storeId": {
"type": "string",
"description": "The store id of the invoice's event",
"nullable": false
},
"invoiceId": {
"type": "string",
"description": "The invoice id of the invoice's event",
"nullable": false
}
}
}
]
},
"WebhookInvoiceConfirmedEvent": {
"description": "Callback sent if the `type` is `InvoiceConfirmed`",
"allOf": [
{
"$ref": "#/components/schemas/WebhookInvoiceEvent"
},
{
"type": "object",
"properties": {
"manuallyMarked": {
"type": "boolean",
"description": "Whether the invoice have been manually marked as confirmed",
"nullable": false
}
}
}
]
},
"WebhookInvoiceInvalidEvent": {
"description": "Callback sent if the `type` is `InvoiceInvalid`",
"allOf": [
{
"$ref": "#/components/schemas/WebhookInvoiceEvent"
},
{
"type": "object",
"properties": {
"manuallyMarked": {
"type": "boolean",
"description": "Whether the invoice have been manually marked as confirmed. If false, this invoice has received payments which could not confirm in time.",
"nullable": false
}
}
}
]
},
"WebhookInvoicePaidEvent": {
"description": "Callback sent if the `type` is `InvoicePaidInFull`",
"allOf": [
{
"$ref": "#/components/schemas/WebhookInvoiceEvent"
},
{
"type": "object",
"properties": {
"overPaid": {
"type": "boolean",
"description": "Whether this invoice has received more money than expected",
"nullable": false
},
"paidAfterExpiration": {
"type": "boolean",
"description": "Whether this invoice has been paid too late",
"nullable": false
}
}
}
]
},
"WebhookInvoiceReceivedPaymentEvent": {
"description": "Callback sent if the `type` is `InvoiceReceivedPayment`",
"allOf": [
{
"$ref": "#/components/schemas/WebhookInvoiceEvent"
},
{
"type": "object",
"properties": {
"afterExpiration": {
"type": "boolean",
"description": "Whether this payment has been sent after expiration of the invoice",
"nullable": false
}
}
}
]
},
"WebhookInvoiceExpiredEvent": {
"description": "Callback sent if the `type` is `InvoiceExpired`",
"allOf": [
{
"$ref": "#/components/schemas/WebhookInvoiceEvent"
},
{
"type": "object",
"properties": {
"partiallyPaid": {
"type": "boolean",
"description": "Whether the invoice received some payments before being expired.",
"nullable": false
}
}
}
]
}
}
},
"x-webhooks": {
"Invoice created": {
"post": {
"summary": "Invoice created",
"description": "A new invoice has been created",
"parameters": [
{
"in": "header",
"name": "BTCPay-Sig",
"required": true,
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
"schema": {
"type": "string",
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookInvoiceEvent"
}
}
}
}
}
},
"Invoice expired": {
"post": {
"summary": "Invoice expired",
"description": "An invoice expired",
"parameters": [
{
"in": "header",
"name": "BTCPay-Sig",
"required": true,
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
"schema": {
"type": "string",
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookInvoiceExpiredEvent"
}
}
}
}
}
},
"Payment received": {
"post": {
"summary": "Payment received",
"description": "An invoice received a payment",
"parameters": [
{
"in": "header",
"name": "BTCPay-Sig",
"required": true,
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
"schema": {
"type": "string",
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookInvoiceReceivedPaymentEvent"
}
}
}
}
}
},
"Invoice paid": {
"post": {
"summary": "Invoice paid",
"description": "An invoice has been fully paid",
"parameters": [
{
"in": "header",
"name": "BTCPay-Sig",
"required": true,
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
"schema": {
"type": "string",
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookInvoicePaidEvent"
}
}
}
}
}
},
"Invoice invalid": {
"post": {
"summary": "Invoice invalid",
"description": "An invoice became invalid",
"parameters": [
{
"in": "header",
"name": "BTCPay-Sig",
"required": true,
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
"schema": {
"type": "string",
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookInvoiceInvalidEvent"
}
}
}
}
}
},
"Invoice confirmed": {
"post": {
"summary": "Invoice confirmed",
"description": "An invoice has been confirmed, order considered settled",
"parameters": [
{
"in": "header",
"name": "BTCPay-Sig",
"required": true,
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
"schema": {
"type": "string",
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookInvoiceConfirmedEvent"
}
}
}
}
}
}
},