PHPackages                             deployecommerce/module-prevent-order-placement - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. deployecommerce/module-prevent-order-placement

ActiveMagento2-module[Validation &amp; Sanitization](/categories/validation)

deployecommerce/module-prevent-order-placement
==============================================

Block Magento 2 order placement when billing or shipping address fields match an admin-configured blocklist. Includes an admin match-count preview and a per-block audit log.

1.0.2(3w ago)344↑33.3%1MITPHPPHP &gt;=8.1.0

Since May 15Pushed 3w agoCompare

[ Source](https://github.com/DeployEcommerce/module-prevent-order-placement)[ Packagist](https://packagist.org/packages/deployecommerce/module-prevent-order-placement)[ RSS](/packages/deployecommerce-module-prevent-order-placement/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (7)Versions (6)Used By (0)

DeployEcommerce\_PreventOrderPlacement
======================================

[](#deployecommerce_preventorderplacement)

Block Magento 2 order placement when a customer's billing or shipping address matches an admin-configured blocklist. Built to stop repeat-offender fraud patterns (drop addresses, recycled phone numbers) at the `placeOrder` boundary before an order is created.

- **Module**: `DeployEcommerce_PreventOrderPlacement`
- **Composer**: `deployecommerce/module-prevent-order-placement`
- **License**: [MIT](LICENSE.md)
- **Tested with**: Magento 2.4.6 (Enterprise) on PHP 8.1

---

Features
--------

[](#features)

- **Admin-managed blocklist** with one rule per row. Each row may populate any subset of `street`, `city`, `county`, `postcode`, `phone` and choose whether the rule applies to the **billing**, **shipping**, or **both** address sides.
- **AND semantics within a row**: all populated fields must match for the row to fire. Empty fields are ignored.
- **Substring matching** on lowercase-normalised values, so admin-entered fragments still catch realistic address variants. Phone values are compared digit-only on both sides, so `+44 7700 900123`, `07700 900123` and `(07700) 900-123` are all matched by the same rule.
- **Generic customer-facing error**: blocked customers see a non-specific message ("Your order cannot be placed. Please contact customer support.") so no detection signal is leaked to fraudsters.
- **Audit log table** records every block with the input data compared, full billing + shipping snapshots, the matched rule, scope, failure reason, IP, quote id, customer id/email, reserved increment id, and the payment method in use at the time. Survives even if downstream order creation logic changes.
- **Admin preview** of how many existing records a rule would match. Before saving, blurring a row sends an AJAX request that scans the last 12 months of `sales_order_address` plus all active `quote_address` rows, and renders a colour-coded inline note:
    - 🟢 Green when both percentages are &lt; 5 %
    - 🟠 Orange between 5 % and 10 %, or when the rule is too loose to estimate (e.g. a single-character field)
    - 🔴 Red above 10 %
- **Per-store kill switch**: a Yes/No toggle at Store View scope lets admins disable the feature anywhere it misbehaves without touching the rules.
- **Defaults to off**, so deploying the module is inert until an admin opts in.

---

Installation
------------

[](#installation)

### Via Composer (recommended)

[](#via-composer-recommended)

```
composer require deployecommerce/module-prevent-order-placement
bin/magento module:enable DeployEcommerce_PreventOrderPlacement
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
```

### Via `app/code`

[](#via-appcode)

Drop the module under `app/code/DeployEcommerce/PreventOrderPlacement/`, add `'DeployEcommerce_PreventOrderPlacement' => 1,` to `app/etc/config.php`, then:

```
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
```

The declarative schema creates one new table: `deployecommerce_preventorderplacement_blocked_attempt`.

---

Configuration
-------------

[](#configuration)

After installation the module appears under **Stores → Configuration → Deploy Ecommerce → Prevent Order Placement**.

### General

[](#general)

SettingScopeDefaultDescriptionEnabledStore ViewNoMaster switch. When **No**, the order-placement check is skipped entirely on the affected store view.### Address Blocklist → Rules

[](#address-blocklist--rules)

Click **Add Rule** for each blocklist entry. Within a row, fill the fields you want matched and leave the rest blank; all populated fields must match (AND) for the rule to fire. Address Scope picks which side of the address is checked.

FieldNotesStreetSubstring match (case-insensitive).CitySubstring match (case-insensitive).CountySubstring match against the address's `region` field.PostcodeSubstring match (case-insensitive).PhoneCompared digit-only on both sides, so formatting doesn't matter.Address Scope`Billing`, `Shipping`, or `Both`.Values are normalised at save time (trimmed, lowercased, phone reduced to digits). The preview note under each row tells you how many existing records the rule would have matched in the last 12 months — keep an eye on it: rules with a high match percentage are almost always too loose.

#### Example rule

[](#example-rule)

StreetCityCountyPostcodePhoneAddress Scope`1 Example Street``ZZ1 1ZZ`ShippingBlocks any order whose shipping address contains both `1 Example Street`**and** `ZZ1 1ZZ`. A second rule covering just the postcode would catch formatting variants of the same drop address.

---

How it works
------------

[](#how-it-works)

A plugin on `Magento\Quote\Api\CartManagementInterface::placeOrder` runs before the order is created and:

1. Bails immediately if the feature is disabled for the quote's store view (using the current request's store, so no quote/config work happens on stores where the feature is off).
2. Loads the configured rule list. Each rule's fields are pre-normalised at save time, so the runtime can do a cheap lowercase `str_contains` / digit-only comparison.
3. For each rule, pulls the billing and/or shipping address from the quote (per the rule's scope) and applies all populated criteria with AND semantics. The first matching rule wins.
4. On match: writes an audit row to `deployecommerce_preventorderplacement_blocked_attempt`, logs a `WARNING` line, and throws a generic `LocalizedException` so the customer sees no detection signal.

The plugin uses `?PaymentInterface $paymentMethod = null` (explicit nullable) to remain warning-clean on PHP ≥ 8.4 while still matching the implicit-nullable signature on the wrapped Magento interface.

---

Audit table
-----------

[](#audit-table)

`deployecommerce_preventorderplacement_blocked_attempt`

ColumnNotes`entity_id`Primary key.`created_at`Time of attempt.`remote_ip`Customer IP (IPv4 or IPv6).`quote_id`The quote at the moment of attempt.`reserved_order_id`Magento increment ID if reserved by the checkout flow; null otherwise. The plugin never forces reservation, so there's no increment-sequence gap.`payment_method`The payment method code in use at the moment of block.`customer_id`Customer id (null for guest checkout).`customer_email`Customer or billing email.`input_data`JSON of the lowercased / digit-only values that were actually compared.`addresses_json`JSON snapshot of the full billing + shipping address rows.`matched_rule`JSON of the rule that fired (post-normalisation).`matched_scope``billing` or `shipping`.`failure_reason`Human-readable string listing each rule field that matched and the value it matched against.There is intentionally no admin grid in this release. The table is queryable directly (phpMyAdmin / `bin/mysql`) and is the canonical record of why an order was refused.

---

Tests
-----

[](#tests)

The module ships with a PEST suite (PHPUnit 9.6 under the hood) covering the matcher, the config-backend normalisation, and the preview estimator (including its SQL filter shape).

```
vendor/bin/pest -c app/code/DeployEcommerce/PreventOrderPlacement/Tests/Unit/phpunit.xml.dist
```

All fixtures use reserved-fictional values (`ZZ1 1ZZ` from Royal Mail's test-postcode range, `07700 900xxx` from Ofcom's drama-use phone range) so no production data is embedded in the test suite.

---

Troubleshooting
---------------

[](#troubleshooting)

- **Section doesn't appear in admin sidebar** — confirm the module is enabled (`bin/magento module:status DeployEcommerce_PreventOrderPlacement`) and the admin user's role has access to `DeployEcommerce_PreventOrderPlacement::config` (granted automatically to full-access roles).
- **Legitimate orders being blocked** — find the row in `deployecommerce_preventorderplacement_blocked_attempt`; the `failure_reason` column lists the exact rule fields that matched. Either tighten the rule (add more fields so AND requires more specificity) or flip the per-store **Enabled** switch to No while you triage.
- **Preview always shows orange "too loose to estimate"** — a populated rule field is shorter than two characters. Lengthen the input; the gate exists to stop per-blur full-table scans on the historical data set.
- **Preview times out** — the 12-month order window covers `~131k` rows on a typical mid-volume store; if your `sales_order_address`is much larger you may want to add a covering index on `created_at`.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance95

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

25d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/254889?v=4)[Scott](/maintainers/ssx)[@ssx](https://github.com/ssx)

![](https://avatars.githubusercontent.com/u/2587545?v=4)[Nathan Chick](/maintainers/nathanchick)[@nathanchick](https://github.com/nathanchick)

---

Top Contributors

[![ssx](https://avatars.githubusercontent.com/u/254889?v=4)](https://github.com/ssx "ssx (5 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/deployecommerce-module-prevent-order-placement/health.svg)

```
[![Health](https://phpackages.com/badges/deployecommerce-module-prevent-order-placement/health.svg)](https://phpackages.com/packages/deployecommerce-module-prevent-order-placement)
```

###  Alternatives

[mollie/magento2

Mollie Payment Module for Magento 2

1131.8M12](/packages/mollie-magento2)[loki/magento2-components

Core module for defining Alpine.js components with advanced AJAX features

1010.0k22](/packages/loki-magento2-components)[run-as-root/magento2-prometheus-exporter

Magento2 Prometheus Exporter

68353.9k](/packages/run-as-root-magento2-prometheus-exporter)[amzn/amazon-pay-magento-2-module

Official Magento2 Plugin to integrate with Amazon Pay

108521.2k1](/packages/amzn-amazon-pay-magento-2-module)[dotdigital/dotdigital-magento2-extension

Dotdigital for Magento 2

50390.4k20](/packages/dotdigital-dotdigital-magento2-extension)[buckaroo/magento2

Buckaroo Magento 2 extension

32414.8k7](/packages/buckaroo-magento2)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
