diff --git a/BTCPayServer.Plugins.Grin/Services/GrinPaymentMonitorService.cs b/BTCPayServer.Plugins.Grin/Services/GrinPaymentMonitorService.cs index e61ec25..94ae159 100644 --- a/BTCPayServer.Plugins.Grin/Services/GrinPaymentMonitorService.cs +++ b/BTCPayServer.Plugins.Grin/Services/GrinPaymentMonitorService.cs @@ -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"); diff --git a/BTCPayServer.Plugins.Grin/Services/GrinService.cs b/BTCPayServer.Plugins.Grin/Services/GrinService.cs index 3f758ab..ee0dddb 100644 --- a/BTCPayServer.Plugins.Grin/Services/GrinService.cs +++ b/BTCPayServer.Plugins.Grin/Services/GrinService.cs @@ -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); diff --git a/BTCPayServer.Plugins.Grin/Services/GrinWebhookPayload.cs b/BTCPayServer.Plugins.Grin/Services/GrinWebhookPayload.cs new file mode 100644 index 0000000..6227ad8 --- /dev/null +++ b/BTCPayServer.Plugins.Grin/Services/GrinWebhookPayload.cs @@ -0,0 +1,40 @@ +using BTCPayServer.Plugins.Grin.Data; + +namespace BTCPayServer.Plugins.Grin.Services; + +/// +/// Single source of truth for the webhook JSON payload, shared by both dispatch +/// paths — (checkout / broadcast → +/// InvoiceProcessing) and (settled / +/// invalid / expired). Previously each path built its own anonymous object and +/// they had drifted: the monitor omitted the top-level invoiceId/ +/// storeId and confirmations, and used metadata.medusa_cart_id +/// while the checkout path used metadata.order_id. Consumers that handle +/// both broadcast and settlement events therefore had to special-case two +/// shapes (and any keyed only on order_id silently missed settlements). +/// +/// To stay backward compatible, the order reference is emitted under BOTH +/// order_id and medusa_cart_id (same value). +/// +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 ?? "", + } + } + }; +}