feat: Implement Slack, Trello, and Courier (ShipLogic) integration

- Add 14 domain events for order lifecycle (OrderCreated, OrderPacked, ShipmentCreated, ParcelDelivered, etc.)
- Create SlackNotifierService with channels for orders, design, production, shipping, ops-alerts
- Create TrelloService to create cards, move cards between lists, attach files, check items
- Create CourierService to integrate with ShipLogic API for shipment creation and document retrieval
- Create PackingController to explicitly capture packing dimensions and weight
- Create ShippingController with multi-layer guards: packing validation, payment/approval verification, idempotency
- Create TrelloWebhookController to handle incoming Trello webhooks as intent signals
- Create CourierWebhookController to handle Shiplogic status updates
- Create event listeners for Slack notifications and Trello card updates
- Create EventServiceProvider to register all events and listeners
- Add database migration for packing, courier, and Trello data columns
- Create config files for slack, trello, and courier integration
- Update .env with integration secrets placeholders
- Add routes for /orders/{id}/pack, /orders/{id}/ship, /api/webhooks/trello, /api/webhooks/courier

Key architectural decisions:
- Packing is explicit ops action (not automatic from status)
- Shipment creation only after: Trello intent + packing confirmed + payment/approval rules met
- Courier API failures keep order in Ready to Ship state (safe retry)
- Trello and Slack are mirrors of backend state, not decision makers
- All side effects flow through event listeners, maintaining separation of concerns
This commit is contained in:
twotalesanimation
2026-01-02 13:47:31 +02:00
parent 4730f5770c
commit 12aadfd917
35 changed files with 1815 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
<?php
namespace App\Providers;
use App\Events\BalancePaid;
use App\Events\DepositPaid;
use App\Events\OrderCreated;
use App\Events\OrderPacked;
use App\Events\ParcelCollected;
use App\Events\ParcelDelivered;
use App\Events\ParcelFailedDelivery;
use App\Events\ParcelInTransit;
use App\Events\ProofApproved;
use App\Events\ProofRevisionRequested;
use App\Events\ProofUploaded;
use App\Events\ShipmentCreated;
use App\Events\ShipmentCreationFailed;
use App\Listeners\NotifySlackOnOrderCreated;
use App\Listeners\NotifySlackOnOrderPacked;
use App\Listeners\NotifySlackOnParcelCollected;
use App\Listeners\NotifySlackOnParcelDelivered;
use App\Listeners\NotifySlackOnParcelFailedDelivery;
use App\Listeners\NotifySlackOnShipmentCreated;
use App\Listeners\NotifySlackOnShipmentCreationFailed;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
// Order events
OrderCreated::class => [
NotifySlackOnOrderCreated::class,
],
// Packing events
OrderPacked::class => [
NotifySlackOnOrderPacked::class,
],
// Shipment events
ShipmentCreated::class => [
NotifySlackOnShipmentCreated::class,
],
ShipmentCreationFailed::class => [
NotifySlackOnShipmentCreationFailed::class,
],
// Courier events
ParcelCollected::class => [
NotifySlackOnParcelCollected::class,
],
ParcelInTransit::class => [
// TODO: Add listener
],
ParcelDelivered::class => [
NotifySlackOnParcelDelivered::class,
],
ParcelFailedDelivery::class => [
NotifySlackOnParcelFailedDelivery::class,
],
// Custom order events (listeners TODO)
DepositPaid::class => [
// TODO: NotifySlackOnDepositPaid
],
ProofUploaded::class => [
// TODO: NotifySlackOnProofUploaded
],
ProofApproved::class => [
// TODO: NotifySlackOnProofApproved
],
ProofRevisionRequested::class => [
// TODO: NotifySlackOnProofRevisionRequested
],
BalancePaid::class => [
// TODO: NotifySlackOnBalancePaid
],
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*/
public function boot(): void
{
//
}
}