// ============================================================================== // MORPH-SPEC - Asaas Webhook Controller Template // Controller para receber webhooks do Asaas // ============================================================================== using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace {{Namespace}}.Web.Controllers; // ============================================================================== // CONTROLLER // ============================================================================== [ApiController] [Route("api/webhooks/asaas")] public class AsaasWebhookController : ControllerBase { private readonly IAsaasWebhookHandler _webhookHandler; private readonly ILogger _logger; public AsaasWebhookController( IAsaasWebhookHandler webhookHandler, ILogger logger) { _webhookHandler = webhookHandler; _logger = logger; } [HttpPost] public async Task HandleWebhook([FromBody] AsaasWebhookPayload payload) { _logger.LogInformation("Received Asaas webhook: {Event}", payload.Event); try { await _webhookHandler.HandleAsync(payload); return Ok(); } catch (Exception ex) { _logger.LogError(ex, "Error processing Asaas webhook: {Event}", payload.Event); // Retorna 200 para evitar retentativas desnecessárias // O erro será tratado internamente return Ok(); } } } // ============================================================================== // WEBHOOK HANDLER INTERFACE // ============================================================================== public interface IAsaasWebhookHandler { Task HandleAsync(AsaasWebhookPayload payload, CancellationToken ct = default); } // ============================================================================== // WEBHOOK HANDLER IMPLEMENTATION // ============================================================================== public class AsaasWebhookHandler : IAsaasWebhookHandler { private readonly IPaymentService _paymentService; private readonly ISubscriptionService _subscriptionService; private readonly ILogger _logger; public AsaasWebhookHandler( IPaymentService paymentService, ISubscriptionService subscriptionService, ILogger logger) { _paymentService = paymentService; _subscriptionService = subscriptionService; _logger = logger; } public async Task HandleAsync(AsaasWebhookPayload payload, CancellationToken ct = default) { _logger.LogInformation("Processing Asaas webhook: {Event} for {PaymentId}", payload.Event, payload.Payment?.Id ?? "N/A"); switch (payload.Event) { // ========================================================= // PAYMENT EVENTS // ========================================================= case AsaasWebhookEvents.PaymentCreated: await HandlePaymentCreatedAsync(payload, ct); break; case AsaasWebhookEvents.PaymentAwaitingRiskAnalysis: await HandlePaymentAwaitingRiskAsync(payload, ct); break; case AsaasWebhookEvents.PaymentPending: await HandlePaymentPendingAsync(payload, ct); break; case AsaasWebhookEvents.PaymentConfirmed: case AsaasWebhookEvents.PaymentReceived: await HandlePaymentConfirmedAsync(payload, ct); break; case AsaasWebhookEvents.PaymentOverdue: await HandlePaymentOverdueAsync(payload, ct); break; case AsaasWebhookEvents.PaymentRefunded: case AsaasWebhookEvents.PaymentRefundInProgress: await HandlePaymentRefundedAsync(payload, ct); break; case AsaasWebhookEvents.PaymentChargebackRequested: case AsaasWebhookEvents.PaymentChargebackDispute: await HandlePaymentChargebackAsync(payload, ct); break; case AsaasWebhookEvents.PaymentDeleted: await HandlePaymentDeletedAsync(payload, ct); break; // ========================================================= // SUBSCRIPTION EVENTS // ========================================================= case AsaasWebhookEvents.SubscriptionCreated: await HandleSubscriptionCreatedAsync(payload, ct); break; case AsaasWebhookEvents.SubscriptionUpdated: await HandleSubscriptionUpdatedAsync(payload, ct); break; case AsaasWebhookEvents.SubscriptionDeleted: await HandleSubscriptionDeletedAsync(payload, ct); break; default: _logger.LogWarning("Unhandled Asaas webhook event: {Event}", payload.Event); break; } } // ========================================================================= // PAYMENT HANDLERS // ========================================================================= private async Task HandlePaymentCreatedAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogInformation("Payment created: {PaymentId}", payload.Payment.Id); await _paymentService.SyncPaymentAsync(payload.Payment.Id, ct); } private async Task HandlePaymentAwaitingRiskAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogInformation("Payment awaiting risk analysis: {PaymentId}", payload.Payment.Id); await _paymentService.UpdateStatusAsync(payload.Payment.Id, PaymentStatus.AwaitingRisk, ct); } private async Task HandlePaymentPendingAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogInformation("Payment pending: {PaymentId}", payload.Payment.Id); await _paymentService.UpdateStatusAsync(payload.Payment.Id, PaymentStatus.Pending, ct); } private async Task HandlePaymentConfirmedAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogInformation("Payment confirmed: {PaymentId}, Value: {Value}", payload.Payment.Id, payload.Payment.Value); await _paymentService.ConfirmPaymentAsync(payload.Payment.Id, ct); } private async Task HandlePaymentOverdueAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogWarning("Payment overdue: {PaymentId}", payload.Payment.Id); await _paymentService.MarkOverdueAsync(payload.Payment.Id, ct); } private async Task HandlePaymentRefundedAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogInformation("Payment refunded: {PaymentId}", payload.Payment.Id); await _paymentService.RefundPaymentAsync(payload.Payment.Id, ct); } private async Task HandlePaymentChargebackAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogWarning("Payment chargeback: {PaymentId}", payload.Payment.Id); await _paymentService.HandleChargebackAsync(payload.Payment.Id, ct); } private async Task HandlePaymentDeletedAsync(AsaasWebhookPayload payload, CancellationToken ct) { if (payload.Payment is null) return; _logger.LogInformation("Payment deleted: {PaymentId}", payload.Payment.Id); await _paymentService.DeletePaymentAsync(payload.Payment.Id, ct); } // ========================================================================= // SUBSCRIPTION HANDLERS // ========================================================================= private async Task HandleSubscriptionCreatedAsync(AsaasWebhookPayload payload, CancellationToken ct) { // Subscription events may not have payment data _logger.LogInformation("Subscription created via webhook"); // Implement as needed } private async Task HandleSubscriptionUpdatedAsync(AsaasWebhookPayload payload, CancellationToken ct) { _logger.LogInformation("Subscription updated via webhook"); // Implement as needed } private async Task HandleSubscriptionDeletedAsync(AsaasWebhookPayload payload, CancellationToken ct) { _logger.LogInformation("Subscription deleted via webhook"); // Implement as needed } } // ============================================================================== // WEBHOOK EVENTS // ============================================================================== public static class AsaasWebhookEvents { // Payment events public const string PaymentCreated = "PAYMENT_CREATED"; public const string PaymentAwaitingRiskAnalysis = "PAYMENT_AWAITING_RISK_ANALYSIS"; public const string PaymentPending = "PAYMENT_PENDING"; public const string PaymentConfirmed = "PAYMENT_CONFIRMED"; public const string PaymentReceived = "PAYMENT_RECEIVED"; public const string PaymentOverdue = "PAYMENT_OVERDUE"; public const string PaymentRefunded = "PAYMENT_REFUNDED"; public const string PaymentRefundInProgress = "PAYMENT_REFUND_IN_PROGRESS"; public const string PaymentChargebackRequested = "PAYMENT_CHARGEBACK_REQUESTED"; public const string PaymentChargebackDispute = "PAYMENT_CHARGEBACK_DISPUTE"; public const string PaymentDeleted = "PAYMENT_DELETED"; // Subscription events public const string SubscriptionCreated = "SUBSCRIPTION_CREATED"; public const string SubscriptionUpdated = "SUBSCRIPTION_UPDATED"; public const string SubscriptionDeleted = "SUBSCRIPTION_DELETED"; } // ============================================================================== // WEBHOOK PAYLOAD // ============================================================================== public record AsaasWebhookPayload { [JsonPropertyName("event")] public string Event { get; init; } = string.Empty; [JsonPropertyName("payment")] public AsaasWebhookPayment? Payment { get; init; } } public record AsaasWebhookPayment { [JsonPropertyName("id")] public string Id { get; init; } = string.Empty; [JsonPropertyName("customer")] public string Customer { get; init; } = string.Empty; [JsonPropertyName("value")] public decimal Value { get; init; } [JsonPropertyName("status")] public string Status { get; init; } = string.Empty; [JsonPropertyName("billingType")] public string BillingType { get; init; } = string.Empty; [JsonPropertyName("externalReference")] public string? ExternalReference { get; init; } [JsonPropertyName("subscription")] public string? Subscription { get; init; } } // ============================================================================== // PAYMENT STATUS ENUM // ============================================================================== public enum PaymentStatus { Pending, AwaitingRisk, Confirmed, Received, Overdue, Refunded, Chargeback, Deleted } // ============================================================================== // SERVICE INTERFACES (to be implemented) // ============================================================================== public interface IPaymentService { Task SyncPaymentAsync(string asaasPaymentId, CancellationToken ct = default); Task UpdateStatusAsync(string asaasPaymentId, PaymentStatus status, CancellationToken ct = default); Task ConfirmPaymentAsync(string asaasPaymentId, CancellationToken ct = default); Task MarkOverdueAsync(string asaasPaymentId, CancellationToken ct = default); Task RefundPaymentAsync(string asaasPaymentId, CancellationToken ct = default); Task HandleChargebackAsync(string asaasPaymentId, CancellationToken ct = default); Task DeletePaymentAsync(string asaasPaymentId, CancellationToken ct = default); } public interface ISubscriptionService { // Implement as needed } // ============================================================================== // DEPENDENCY INJECTION // ============================================================================== public static class AsaasWebhookServiceExtensions { public static IServiceCollection AddAsaasWebhook(this IServiceCollection services) { services.AddScoped(); return services; } }