order-management 0.0.95 → 0.0.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,7 @@ export default defineConfig({
47
47
  orderManagement.modules["payment-detail"],
48
48
  orderManagement.modules["refund-payment-mapping"],
49
49
  orderManagement.modules["cancel-order-reason"],
50
+ orderManagement.modules["deleted-records-audit"],
50
51
  ],
51
52
  })
52
53
  ```
@@ -179,6 +180,9 @@ Most production values are expected to be supplied via plugin options in `medusa
179
180
  | GET | `/admin/orders/:order_id/cancel-reason-mapping` | Admin JWT | Cancel reason mapping for order. |
180
181
  | PUT | `/admin/refund-payment-mapping/:return_id` | Admin JWT | Update `refund_mode`, `images`, `details`. |
181
182
  | POST | `/admin/refund-payment-mapping/:return_id/mark-refunded` | Admin JWT | Set `is_refunded=true` (requires return `received`; 400 `NOT_ALLOWED` otherwise). |
183
+ | GET | `/admin/deleted-records-audit` | Admin JWT | Paginated hard-delete audit entries (no `old_data`). See [Deleted Records audit](#deleted-records-audit). |
184
+ | GET | `/admin/deleted-records-audit/stats` | Admin JWT | All-time counts for summary cards plus known `source` / `deleted_by` values. |
185
+ | GET | `/admin/deleted-records-audit/:id` | Admin JWT | One audit entry including the full `old_data` snapshot. |
182
186
 
183
187
  Important endpoint examples:
184
188
 
@@ -208,6 +212,7 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
208
212
  | `PaymentDetailModuleService` | Payment detail CRUD and default constraints | `createPaymentDetail`, `updatePaymentDetail`, `deletePaymentDetail`, `makeDefault`, `hasDefaultPayment`, `listPaymentDetailsByCustomer` |
209
213
  | `RefundPaymentMappingModuleService` | Return-to-payment mapping management | `createMapping`, `getByReturnId`, `updatePaymentId`, `markRefunded`, `updateAdminFields` |
210
214
  | `CancelOrderReasonModuleService` | Stores cancel reason used for an order | `assertReasonExists`, `recordOrderCancelReason`, `getMappingWithReasonLabel` |
215
+ | `DeletedRecordsAuditModuleService` | Reads the hard-delete audit trail | `listAudits`, `retrieveAudit`, `getStats` |
211
216
 
212
217
  ## Workflows & Steps (Medusa v2)
213
218
 
@@ -245,7 +250,7 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
245
250
  | Swaps routes | `/swaps`, `/swaps/:id` | Exchange listing/detail pages | Search, filters, reject/cancel exchange; uses `/admin/swaps*`. |
246
251
  | Payments routes | `/payments`, `/payments/:id` | Transaction list + detail pages | Filter transactions and inspect gateway payload. |
247
252
  | Refunds routes | `/refunds`, `/refunds/:id` | Refund list + detail pages | Filter by status/provider/date/amount; view computed refund context. |
248
- | `order-refund-context-widget` | `order.details.after` | Refund context panel | Order checkout payment method (including guest orders), refund calculation (product amount, return charge, processed final refund), edit refund mode/details/images, upload images, mark refunded. |
253
+ | Deleted Records routes | `/deleted-records-audit`, `/deleted-records-audit/:id` | Audit list + investigation detail | Summary cards, record ID search, entity/date/source/deleted-by filters in the URL; detail shows record summary, masked raw data and next steps. |
249
254
  | `order-gift-items-widget` | `order.details.side.after` | Gift item widget | Reads gift metadata and downloads generated gift card image. |
250
255
 
251
256
  ## Models & Entities
@@ -255,6 +260,7 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
255
260
  | `payment_detail` | `id`, `customer_id`, `type`, `detail_json`, `is_default` | Linked logically to Medusa customer via `customer_id`. |
256
261
  | `refund_payment_mapping` | `id`, `return_id` (unique), `payment_id?`, `is_refunded`, `refund_mode?`, `images`, `details?` | Links a return to refund destination and admin reconciliation metadata. |
257
262
  | `cancel_order_reason_mapping` | `id`, `order_id`, `reason_id` | Migration defines FK to `return_reason(id)`. |
263
+ | `deleted_records_audit` | `id`, `table_name`, `record_id?`, `operation`, `old_data` (jsonb), `occurred_at`, `deleted_by?`, `source?` | Append-only; written by Postgres `BEFORE DELETE` triggers, never by application code. |
258
264
 
259
265
  ## Use Cases & Examples
260
266
 
@@ -278,6 +284,8 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
278
284
  | Guest return shows no payment method in Admin | Guest COD returns previously stored `payment_id=null` and guests could not create/link payment details | Upgrade this plugin. Pass `refund_payment` / `refund_payment_id` on guest return create, or use `POST /store/guest-orders/:id/payment-details` + `PUT .../returns/:return_id/refund-payment`. |
279
285
  | Cannot set default payment via create/update | API intentionally blocks `is_default` in request body | Use `POST /store/payment-details/:id/make-default`. |
280
286
  | No development stack traces in API errors | `NODE_ENV` not `development` | Set `NODE_ENV=development` locally when debugging. |
287
+ | Deleted Records page is empty after deletes | `deleted-records-audit` module not registered/migrated, or rows were soft-deleted (`deleted_at` set) rather than hard-deleted | Register the module and run `npx medusa db:migrate`. Only real SQL `DELETE`s are captured. |
288
+ | Deleted Records shows "Unknown" for Deleted by / Source | The app didn't set `app.audit_actor_id` / `app.audit_source` in the deleting transaction | Expected by default. See [Recording who deleted a record](#recording-who-deleted-a-record). |
281
289
 
282
290
 
283
291
  <p align="center">
@@ -335,6 +343,7 @@ This plugin is compatible with Medusa v2 (e.g. `@medusajs/framework` and `@medus
335
343
  - **Medusa workflows**: Customer returns use `createAndCompleteReturnOrderWorkflow`; customer exchanges use `beginExchangeOrderWorkflow`, `orderExchangeRequestItemReturnWorkflow`, `orderExchangeAddNewItemWorkflow`, `confirmExchangeRequestWorkflow`, and `cancelBeginOrderExchangeWorkflow`. Admin actions use `confirmReturnReceiveWorkflow`, `cancelReturnWorkflow`, and `cancelBeginOrderExchangeWorkflow`.
336
344
  - **Payment details**: Custom module `payment_detail` for customer payment methods (UPI, bank, card). Store CRUD at `/store/payment-details`. Type-driven validation — UPI requires `upi_id`; bank requires `account_holder_name`, `bank_name`, `account_number`, `ifsc` (optional `branch_name`); card requires `card_holder_name`, `card_number`, `expiry_date`, `cvv`. One default per customer; return creation requires at least one default payment method.
337
345
  - **Refund payment mapping**: Table `refund_payment_mapping` (one row per return: `return_id`, `payment_id`, `is_refunded`, `refund_mode`, `images`, `details`). A mapping is created automatically when a return is created. Customer can GET mapping (read-only for all fields) and PUT `payment_id` only while `is_refunded = false`; customer can also GET all mappings for an order to see if all refunds are successful. Only admin can set `is_refunded`, `refund_mode` (offline/online), `images`, and `details`.
346
+ - **Deleted Records audit**: Postgres `BEFORE DELETE` triggers on 190 commerce/plugin tables copy every hard-deleted row into `deleted_records_audit`. An Admin page (**Deleted Records**) lets admins search by record ID, filter by entity/date/source, read a human-readable summary of what was deleted, inspect the full (secret-masked) snapshot, and see what to do next. See [Deleted Records audit](#deleted-records-audit).
338
347
  - **Receive-before-refund**: Admin payment refunds (`POST /admin/payments/:id/refund`) and `POST /admin/refund-payment-mapping/:return_id/mark-refunded` are rejected with `400 NOT_ALLOWED` while any return on the order is `open`, `requested`, or `partially_received` (including exchange-linked returns). Receive items first. Refunds with no unreceived return (order cancel / goodwill) still work. Receive-and-refund inside Medusa’s receive workflow is not blocked.
339
348
 
340
349
  ### Plugin structure (high level)
@@ -592,10 +601,78 @@ The plugin includes a `refund_payment_mapping` module: one row per return linkin
592
601
  - `PUT /admin/refund-payment-mapping/:return_id` — update admin-only fields. Body: `{ refund_mode?: "offline" | "online" | null, images?: string[], details?: string | null }` (partial; only provided keys are updated).
593
602
  - `POST /admin/refund-payment-mapping/:return_id/mark-refunded` — set `is_refunded = true` (idempotent). Requires Medusa return status `received`; otherwise `400 NOT_ALLOWED`. Already-refunded mappings still succeed without re-checking receive status.
594
603
  - **Receive-before-refund guard**: Middleware on Medusa core `POST /admin/payments/:id/refund` rejects the request with `400 NOT_ALLOWED` when the order has any return in `open`, `requested`, or `partially_received` (including exchange-linked returns). Message: `Cannot process a refund while the return is {status}. Receive the returned items first.` Orders with no return, or only `received` / `canceled` returns, are not blocked.
595
- - **Order detail integration**: `GET /admin/orders/:order_id/refund-context` — aggregated read returning `return_id`, `return_status`, `can_process_refund` (`true` only when status is `received` and the mapping exists and is not already refunded), `order_payment` (checkout `provider_id`, human-readable `label`, `is_cod` — used for guest orders that have no saved `payment_detail`), `refund_mapping` (id, payment_id, is_refunded, refund_mode, images, details), `payment_detail` (id, type, detail_json, is_default) resolved from the customer's specified or default payment detail, and `refund` (product_amount, return_charge, expected_net_amount, final_refund_amount, currency_code, is_processed, breakdown_matches, destination_label). `final_refund_amount` is the processed payment refund (or COD/offline `return.refund_amount` after mark-refunded), not `product_amount - return_charge`. Returns null blocks when no return or mapping exists. An admin widget at `order.details.after` shows **Order payment method** (the provider used at checkout, including guest orders), **Refund calculation**, refund payment method, **Refund mode** (Offline/Online), **Details**, **Images** (with upload via Medusa `POST /admin/uploads`), and **Mark refunded** only when `can_process_refund` is true. While the return is unreceived the widget tells admins to receive items first and not refund from Payments. Only admin can change refund_mode, images, and details.
604
+ - **Order detail integration**: `GET /admin/orders/:order_id/refund-context` — aggregated read returning `return_id`, `return_status`, `can_process_refund` (`true` only when status is `received` and the mapping exists and is not already refunded), `order_payment` (checkout `provider_id`, human-readable `label`, `is_cod` — used for guest orders that have no saved `payment_detail`), `refund_mapping` (id, payment_id, is_refunded, refund_mode, images, details), `payment_detail` (id, type, detail_json, is_default) resolved from the customer's specified or default payment detail, and `refund` (product_amount, return_charge, expected_net_amount, final_refund_amount, currency_code, is_processed, breakdown_matches, destination_label). `final_refund_amount` is the processed payment refund (or COD/offline `return.refund_amount` after mark-refunded), not `product_amount - return_charge`. Returns null blocks when no return or mapping exists. Used by admin refund/return detail pages; refund_mode, images, and details are admin-only via the mapping APIs above.
596
605
  - **Store return payload**: `GET /store/returns` and `GET /store/returns/:id` attach the same `refund` object so the storefront can render the breakdown. `GET /store/orders/:order_id/refund-payment-mappings` includes `refund` per mapping.
597
606
  - **Registration**: Add the module in `medusa-config.js` (e.g. `orderManagement.modules["refund-payment-mapping"]`) and run migrations.
598
607
 
608
+ ### Deleted Records audit
609
+
610
+ A hard `DELETE` normally leaves no trace. The `deleted_records_audit` module installs a Postgres trigger on each monitored table that copies the full row into `deleted_records_audit` **inside the same transaction** as the delete, so rolled-back deletes leave no audit row and nothing can be deleted without being recorded.
611
+
612
+ **Setup**
613
+
614
+ 1. Register `orderManagement.modules["deleted-records-audit"]` in `modules` (see [Installation & Setup](#installation--setup)).
615
+ 2. Run `npx medusa db:migrate`.
616
+
617
+ The migration (`Migration20261008120000`) creates the table, the `om_audit_row_delete()` function, and one `om_ad_<table>` trigger per table in `src/modules/deleted-records-audit/audited-tables.ts` (190 tables covering orders, carts, customers, catalog, pricing, payments, fulfillment, inventory, wallet, auth, store settings, engagement, notifications and system tables). Tables that don't exist in your database are skipped, so the migration is safe on smaller installs. Re-running it is idempotent; rolling it back removes every trigger, the function and the table.
618
+
619
+ **What is captured**
620
+
621
+ | Column | Meaning |
622
+ |---|---|
623
+ | `table_name` | Table the row was deleted from. Shown in the UI as a human entity name (e.g. `order_line_item` → "Order Item"). |
624
+ | `record_id` | The deleted row's `id`. `null` for link tables without an `id` column. |
625
+ | `old_data` | Full row as JSON, exactly as it was immediately before deletion. |
626
+ | `occurred_at` | When the delete ran. (Not `deleted_at`: that column is Medusa's soft-delete marker.) |
627
+ | `deleted_by`, `source` | Optional attribution—see below. |
628
+
629
+ Soft deletes (`UPDATE … SET deleted_at = now()`) are **not** captured; only real SQL `DELETE`s are.
630
+
631
+ #### Recording who deleted a record
632
+
633
+ The trigger reads two Postgres settings from the deleting transaction. If your code sets them, the UI shows who deleted the record and where the deletion came from; otherwise both show "Unknown".
634
+
635
+ ```sql
636
+ BEGIN;
637
+ SET LOCAL app.audit_actor_id = 'user_01H...'; -- shown as "Deleted by"
638
+ SET LOCAL app.audit_source = 'admin'; -- admin | api | system | job | workflow | any label
639
+ DELETE FROM product WHERE id = 'prod_123';
640
+ COMMIT;
641
+ ```
642
+
643
+ `SET LOCAL` only lasts until the end of the transaction, so the values cannot leak into other requests on a pooled connection.
644
+
645
+ #### Admin UI
646
+
647
+ **Deleted Records** (`/app/deleted-records-audit`) is an investigation tool rather than a raw table viewer:
648
+
649
+ - **Summary cards**: total deleted, today, last 7 days, and the number of entity types affected (all-time, computed from real data).
650
+ - **Search** by record ID (substring, case-insensitive).
651
+ - **Filters**: a searchable, grouped **Entity** picker (pick a whole area such as "All Orders" or a single entity), a **Date** preset (any time, today, yesterday, last 7/30 days, custom range), plus **Source** and **Deleted by** filters, which appear once attribution data exists. Filters live in the URL, so investigations can be bookmarked or shared, and going back from a detail page restores the same filters, loaded rows and position.
652
+ - **Detail page** (`/app/deleted-records-audit/:id`): entity and record ID, a clear *historical snapshot* notice, deletion time/actor/source, a **Record summary** of the important fields for common entities (orders, items, customers, products, payments, refunds, returns, carts, users, API keys, promotions, inventory, reviews…), the full snapshot in a collapsible JSON viewer with **Copy JSON**, and **What you can do now** guidance tailored to the kind of record.
653
+
654
+ There is deliberately **no restore button**: recreating records automatically is unsafe for linked commerce data. The guidance explains how to recreate a record when that's appropriate (and when not to, e.g. payments or wallet balances).
655
+
656
+ **Sensitive data**: passwords, hashes, tokens, secrets, API keys, card/account numbers, CVVs, OTP and recovery codes are masked (`••••••••••••`) in the summary, the JSON viewer and **Copy JSON**. The raw data in the database is unmasked, and the API routes are admin-only.
657
+
658
+ **List API query parameters** (`GET /admin/deleted-records-audit`)
659
+
660
+ | Param | Description |
661
+ |---|---|
662
+ | `limit` (≤ 100, default 50), `offset` | Pagination. Response: `{ audits, count, limit, offset }`. |
663
+ | `q` | Record ID substring (`%` / `_` are matched literally). |
664
+ | `table_name` / `table_names` | One table, or a comma-separated list (used for category filters). |
665
+ | `date_from`, `date_to` | ISO timestamps bounding `occurred_at`. |
666
+ | `source` | Exact source value; `unknown` matches rows with no source. |
667
+ | `deleted_by` | Substring match on `deleted_by`. |
668
+
669
+ `GET /admin/deleted-records-audit/stats?today_start=<ISO>` returns `{ stats: { total, today, last_7_days, tables_affected, sources, deleted_by } }`. Pass the viewer's local midnight as `today_start` so "Today" matches their timezone.
670
+
671
+ **Operational notes**
672
+
673
+ - Every hard delete on a monitored table writes one extra row. High-churn tables (`cart_*`, `workflow_execution`, `notification`, search index tables) can grow the audit table quickly, so plan retention or archiving for large stores.
674
+ - To monitor another table, add it to `AUDITED_TABLE_NAMES` and ship a new migration that calls `buildCreateAuditTriggerSql(table)`. Never add `deleted_records_audit` itself.
675
+
599
676
 
600
677
  ## Return Orders Admin Panel
601
678
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "order-management",
3
- "version": "0.0.95",
3
+ "version": "0.0.97",
4
4
  "description": "A starter for Medusa plugins.",
5
5
  "author": "Medusa (https://medusajs.com)",
6
6
  "license": "MIT",