Context
The billing webhook handler (src/api/billing/webhook.rs) currently calls repositories directly with production-hardened logic including:
- Idempotency checks (
webhook_already_processed(), mark_webhook_processed())
- ProductRepository lookups for plan name resolution from Polar
price_id
- Multiple Polar event variants with field name variations (
customer_id vs customerId, external_id vs externalId)
- Granular error handling (400/401/503 status codes)
BillingService::process_webhook_event() exists but is a simpler abstraction that doesn't replicate this complexity. Refactoring to use it would be a regression.
Task
Create a dedicated WebhookService to handle all webhook-specific logic:
-
New service: src/services/webhook_service.rs
- verify_webhook_signature() — signature verification
- process_webhook_event() — main event dispatcher with idempotency
handle_subscription_created() — subscription creation logic
handle_subscription_updated() — subscription update logic
- handle_subscription_cancelled() — subscription cancellation logic
- Helper methods for billing account resolution, plan mapping, etc.
-
Refactor handler: src/api/billing/webhook.rs
- Delegate to
WebhookService for business logic
- Keep HTTP-specific concerns (request parsing, response formatting, error status mapping) at handler level
-
Clean up: Remove unused webhook methods from BillingService (already done in recent commit)
Benefits
- Consistent architecture: all business logic in service layer
- Reusability: webhook logic can be used by cron job handlers or other consumers
- Testability: easier to unit test webhook logic in isolation
Notes
- The current webhook handler is production-hardened and should not be changed in a way that reduces reliability
- Ensure idempotency and all event variant handling is preserved
- Consider whether trigger_downgrade_job() belongs in WebhookService (for scheduled downgrades) or a separate CronService
Context
The billing webhook handler (src/api/billing/webhook.rs) currently calls repositories directly with production-hardened logic including:
webhook_already_processed(),mark_webhook_processed())price_idcustomer_idvscustomerId,external_idvsexternalId)BillingService::process_webhook_event() exists but is a simpler abstraction that doesn't replicate this complexity. Refactoring to use it would be a regression.
Task
Create a dedicated
WebhookServiceto handle all webhook-specific logic:New service:
src/services/webhook_service.rshandle_subscription_created()— subscription creation logichandle_subscription_updated()— subscription update logicRefactor handler: src/api/billing/webhook.rs
WebhookServicefor business logicClean up: Remove unused webhook methods from BillingService (already done in recent commit)
Benefits
Notes