Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 1 addition & 16 deletions BTCPayServer.Plugins.Grin/Services/GrinPaymentMonitorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,22 +261,7 @@ private async Task DispatchWebhookAsync(GrinInvoice invoice, GrinStoreSettings s

try
{
var amountGrin = invoice.AmountNanogrin / 1_000_000_000m;
var payload = new
{
@event = eventType,
invoice = new
{
id = invoice.Id,
status = invoice.Status.ToString(),
amount = amountGrin,
metadata = new
{
session_id = invoice.SessionId ?? "",
medusa_cart_id = invoice.OrderId ?? "",
}
}
};
var payload = GrinWebhookPayload.Build(invoice, eventType);

var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
Expand Down
19 changes: 1 addition & 18 deletions BTCPayServer.Plugins.Grin/Services/GrinService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,24 +204,7 @@ public async Task DispatchWebhook(GrinStoreSettings settings, GrinInvoice invoic

try
{
var payload = new
{
@event = eventType,
invoiceId = invoice.Id,
storeId = invoice.StoreId,
invoice = new
{
id = invoice.Id,
status = invoice.Status.ToString(),
amount = invoice.AmountNanogrin / 1_000_000_000m,
confirmations = invoice.Confirmations,
metadata = new
{
session_id = invoice.SessionId ?? "",
order_id = invoice.OrderId ?? "",
},
},
};
var payload = GrinWebhookPayload.Build(invoice, eventType);

var json = System.Text.Json.JsonSerializer.Serialize(payload);
var body = System.Text.Encoding.UTF8.GetBytes(json);
Expand Down
40 changes: 40 additions & 0 deletions BTCPayServer.Plugins.Grin/Services/GrinWebhookPayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using BTCPayServer.Plugins.Grin.Data;

namespace BTCPayServer.Plugins.Grin.Services;

/// <summary>
/// Single source of truth for the webhook JSON payload, shared by both dispatch
/// paths — <see cref="GrinService.DispatchWebhook"/> (checkout / broadcast →
/// InvoiceProcessing) and <see cref="GrinPaymentMonitorService"/> (settled /
/// invalid / expired). Previously each path built its own anonymous object and
/// they had drifted: the monitor omitted the top-level <c>invoiceId</c>/
/// <c>storeId</c> and <c>confirmations</c>, and used <c>metadata.medusa_cart_id</c>
/// while the checkout path used <c>metadata.order_id</c>. Consumers that handle
/// both broadcast and settlement events therefore had to special-case two
/// shapes (and any keyed only on <c>order_id</c> silently missed settlements).
///
/// To stay backward compatible, the order reference is emitted under BOTH
/// <c>order_id</c> and <c>medusa_cart_id</c> (same value).
/// </summary>
public static class GrinWebhookPayload
{
public static object Build(GrinInvoice invoice, string eventType) => new
{
@event = eventType,
invoiceId = invoice.Id,
storeId = invoice.StoreId,
invoice = new
{
id = invoice.Id,
status = invoice.Status.ToString(),
amount = invoice.AmountNanogrin / 1_000_000_000m,
confirmations = invoice.Confirmations,
metadata = new
{
session_id = invoice.SessionId ?? "",
order_id = invoice.OrderId ?? "",
medusa_cart_id = invoice.OrderId ?? "",
}
}
};
}