# Architecture — magento-orderwebhook

> **Last verified:** 2026-02-26 (Europe/London)
> **Owner:** Engineering
> **Mermaid standard:** `/modules/magento-orderwebhook/docs/MERMAID_DIAGRAM_STANDARDS.md`

---

## Confirmed Facts

1. Module ID: `PortalPeople_OrderWebhook` — registered in `/modules/magento-orderwebhook/src/etc/module.xml`.
2. Package: `portalpeople/magento-orderwebhook` v1.0.15 (`composer.json`) vs `setup_version="1.0.0"` in `module.xml` — version conflict.
3. Two event observers registered in `src/etc/events.xml`: `sales_order_place_after` → `OrderPlaceAfter`; `sales_order_save_after` → `OrderSaveAfter`.
4. `OrderPlaceAfter` skips admin-created orders by checking `empty($order->getRemoteIp())`. Frontend orders trigger `order_placed` webhook immediately.
5. `OrderSaveAfter` fires only when order status transitions **to** `'processing'` (compares `getOrigData('status')` vs `getStatus()`). Triggers `order_updated` webhook.
6. `WebhookSender` uses `private static bool $sending = false` re-entrancy guard to prevent recursive observer loops when `persistOrder()` calls `orderRepository->save()`.
7. `WebhookSender` retries on HTTP failure using exponential backoff: `sleep(pow(2, $attempt))`, up to `retry_attempts` (default: 3).
8. `WebhookSender::sendRequest()` retrieves `EncryptorInterface` via `\Magento\Framework\App\ObjectManager::getInstance()` — anti-pattern, bypasses Magento DI.
9. `OrderDataBuilder` builds the JSON payload including: event_type, timestamp, store URL, order fields, customer (including DOB and gender), addresses, payment (cc_last4/type/exp), items, shipping method, and `shipping_date`/`shipping_message` (sourced from magento-deliverydates module; null if absent).
10. Three columns added to `sales_order` via `src/etc/db_schema.xml`: `ngauge_order_no` (varchar 255), `ngauge_order_id` (varchar 255), `ngauge_order_link` (text) — all nullable.
11. Once `ngauge_order_id` is set on an order, the admin UI "Send to NGauge" button is hidden. Re-send is only possible via CLI unless the field is manually cleared.
12. Admin UI block `NgaugeFields` uses deprecated `Magento\Framework\Registry` to retrieve the current order.
13. Admin resend available via `ResendWebhook` controller (ACL: `PortalPeople_OrderWebhook::config`); sends `manual_resend` event type.
14. CLI command `order:webhook:test [ORDER_ID]` provided via `TestWebhookCommand` — shows masked config, sends `test_webhook` event.
15. `comment_only` config mode: when enabled, NGauge response is added as order comment only; `ngauge_*` fields are not written to DB.
16. `WebhookSender::persistOrder()` performs a **single** `orderRepository->save()` for both NGauge field updates and the order comment — prevents double-save.
17. API key stored as Magento encrypted config (`Magento\Config\Model\Config\Backend\Encrypted`); decrypted at runtime via `EncryptorInterface`.
18. Default NGauge base URL: `https://ahshub.portalpeople.app` (from `src/etc/config.xml`).
19. `composer.json` `extra.bootstrap` references `PortalPeople\\NgaugeBackupSql\\Module` — wrong module namespace, likely a copy-paste error.
20. No PHPUnit test configuration found. No test files present.

---

## L0 Context Diagram

```mermaid
%% Title: magento-orderwebhook — L0 System Context
%% Layer: L0
%% Owner: Engineering
%% Last verified: 2026-02-26 (Europe/London)
%% Scope: External actors and systems that touch the module
%% Excludes: Internal classes, DB columns, config keys

flowchart LR
classDef external stroke-width:1.5px;
classDef system stroke-width:1.5px;
classDef data stroke-dasharray:3 3,stroke-width:1.5px;

  store_customer[Store Customer]:::external
  admin_user[Admin User]:::external
  ngauge[NGauge Platform]:::external

  subgraph Magento[Magento 2 Platform]
    webhook_module[OrderWebhook Module]:::system
    sales_module[Sales Module]:::system
    sales_order_db[(sales_order DB)]:::data
    config_store[(Config Store)]:::data
  end

  store_customer -->|"Places order"| sales_module
  sales_module -->|"sales_order_place_after"| webhook_module
  sales_module -->|"sales_order_save_after"| webhook_module
  admin_user -->|"Resend / Test webhook"| webhook_module
  webhook_module -->|"POST JSON webhook"| ngauge
  ngauge -->|"Order no / ID / link"| webhook_module
  webhook_module -->|"Write ngauge fields"| sales_order_db
  webhook_module -->|"Read API key / config"| config_store
```

---

## L1 Containers Diagram

```mermaid
%% Title: magento-orderwebhook — L1 Containers
%% Layer: L1
%% Owner: Engineering
%% Last verified: 2026-02-26 (Europe/London)
%% Scope: Major components inside the module and adjacent Magento services
%% Excludes: Internal class methods, DB column definitions

flowchart LR
classDef external stroke-width:1.5px;
classDef system stroke-width:1.5px;
classDef data stroke-dasharray:3 3,stroke-width:1.5px;

  ngauge[NGauge REST API]:::external

  subgraph Magento[Magento 2 Platform]
    subgraph Observers[Observer Layer]
      obs_place[OrderPlaceAfter]:::system
      obs_save[OrderSaveAfter]:::system
    end

    subgraph Core[Core Services]
      sender[WebhookSender]:::system
      builder[OrderDataBuilder]:::system
    end

    subgraph AdminUI[Admin UI]
      resend_ctrl[ResendWebhook Controller]:::system
      ngauge_block[NgaugeFields Block]:::system
    end

    cli[TestWebhookCommand CLI]:::system
    sales_mod[Magento Sales Module]:::system
    sales_db[(sales_order)]:::data
    config_db[(core_config_data)]:::data
  end

  sales_mod -->|"sales_order_place_after"| obs_place
  sales_mod -->|"sales_order_save_after"| obs_save
  obs_place -->|"sendOrderData(order_placed)"| sender
  obs_save -->|"sendOrderData(order_updated)"| sender
  resend_ctrl -->|"sendOrderData(manual_resend)"| sender
  cli -->|"sendOrderData(test_webhook)"| sender
  sender -->|"buildPayload"| builder
  sender -->|"POST JSON"| ngauge
  ngauge -->|"order no / id / link"| sender
  sender -->|"save order + comment"| sales_db
  sender -->|"read config"| config_db
  ngauge_block -->|"read ngauge fields"| sales_db
```

---

## L3 Flow: Frontend Order Placed

```mermaid
sequenceDiagram
  %% Title: magento-orderwebhook — Frontend Order Placed (L3)
  %% Layer: L3
  %% Owner: Engineering
  %% Last verified: 2026-02-26 (Europe/London)
  %% Scope: Order placed by store customer through to NGauge response persisted
  %% Excludes: Payment capture, fulfilment, admin orders

  autonumber
  actor Customer
  participant Magento as Magento Sales
  participant Observer as OrderPlaceAfter
  participant Sender as WebhookSender
  participant Builder as OrderDataBuilder
  participant NGauge as NGauge REST API
  participant DB as sales_order

  Customer->>Magento: Submit checkout
  Magento->>Observer: sales_order_place_after
  Observer->>Observer: Check remote_ip (skip if admin)
  Observer->>Sender: sendOrderData(order, order_placed)
  Sender->>Sender: Check re-entrancy guard
  Sender->>Sender: Check webhook enabled config
  Sender->>Builder: buildOrderData(order, order_placed)
  Builder-->>Sender: JSON payload (incl. DOB, gender, shipping fields)
  Sender->>NGauge: POST webhook (Bearer token, retry up to 3x)
  NGauge-->>Sender: 200 + ngauge_order_no / id / link
  Sender->>DB: persistOrder (ngauge fields + comment, single save)
```

---

## L3 Flow: Admin Resend Webhook

```mermaid
sequenceDiagram
  %% Title: magento-orderwebhook — Admin Resend Webhook (L3)
  %% Layer: L3
  %% Owner: Engineering
  %% Last verified: 2026-02-26 (Europe/London)
  %% Scope: Admin user manually resending webhook for a specific order
  %% Excludes: Observer-triggered flows, CLI test command

  autonumber
  actor Admin
  participant Controller as ResendWebhook Controller
  participant Sender as WebhookSender
  participant Builder as OrderDataBuilder
  participant NGauge as NGauge REST API
  participant DB as sales_order

  Admin->>Controller: GET /admin/.../resend?order_id=X
  Controller->>Controller: ACL check (PortalPeople_OrderWebhook::config)
  Controller->>Sender: sendOrderData(order, manual_resend)
  Sender->>Builder: buildOrderData(order, manual_resend)
  Builder-->>Sender: JSON payload
  Sender->>NGauge: POST webhook
  NGauge-->>Sender: Response
  Sender->>DB: persistOrder (update ngauge fields + comment)
  Controller-->>Admin: Redirect to order view
```

---

## Unknowns

1. `UNKNOWN — Requires Clarification`: `extra.bootstrap` in `composer.json` references `PortalPeople\\NgaugeBackupSql\\Module`. Is this intentional (shared bootstrap) or a copy-paste error?
2. `UNKNOWN — Requires Clarification`: Does `OrderSaveAfter` fire correctly for admin-created orders transitioning to `processing`? Depends on Magento's `orig_data` being populated correctly on admin save.
3. `UNKNOWN — Requires Clarification`: Are `shipping_date` and `shipping_message` populated in production? Requires magento-deliverydates to be installed and active.
4. `UNKNOWN — Requires Clarification`: Is `comment_only` mode in active use in production? Affects whether `ngauge_order_id` is ever written to DB.
5. `UNKNOWN — Requires Clarification`: What is the canonical version of this module? `composer.json` v1.0.15 vs `module.xml` `setup_version="1.0.0"`.

---

## Evidence

- `/modules/magento-orderwebhook/src/etc/module.xml`
- `/modules/magento-orderwebhook/src/etc/events.xml`
- `/modules/magento-orderwebhook/src/etc/db_schema.xml`
- `/modules/magento-orderwebhook/src/etc/di.xml`
- `/modules/magento-orderwebhook/src/etc/config.xml`
- `/modules/magento-orderwebhook/src/Observer/OrderPlaceAfter.php`
- `/modules/magento-orderwebhook/src/Observer/OrderSaveAfter.php`
- `/modules/magento-orderwebhook/src/Model/WebhookSender.php`
- `/modules/magento-orderwebhook/src/Model/OrderDataBuilder.php`
- `/modules/magento-orderwebhook/src/Controller/Adminhtml/Order/ResendWebhook.php`
- `/modules/magento-orderwebhook/src/Console/Command/TestWebhookCommand.php`
- `/modules/magento-orderwebhook/src/Block/Adminhtml/Order/NgaugeFields.php`
- `/modules/magento-orderwebhook/src/view/adminhtml/templates/order/view/ngauge_fields.phtml`
- `/modules/magento-orderwebhook/composer.json`
