ForgeSync: A Ground-Up Sage 100 ERP Integration for Magento 2
Replacing a legacy integration with a modern, maintainable module built on Magento best practices.
The Problem
Our client, a B2B industrial manufacturer running Magento 2, had outgrown their existing ERP integration. The legacy module (originally written by a third party) suffered from:
- Direct ObjectManager calls scattered throughout the codebase
- No service contracts, making the code tightly coupled and untestable
- Fragile import logic that silently dropped records on failure
- No admin visibility into sync status, export queues, or error diagnostics
- Hardcoded assumptions that made extending the module risky
Rather than patch a brittle foundation, we proposed a full rewrite: ForgeSync.
Architecture & Design Principles
ForgeSync was built from scratch with a clear set of priorities:
Dependency Injection Only
Every class is wired through di.xml. No ObjectManager::getInstance() anywhere in the module. This makes the code predictable, testable, and aligned with Magento’s intended architecture.
Service Contracts
All business logic lives behind interfaces in Api/. Import services, export services, and the ERP client all have clean contracts, making it straightforward to swap implementations or mock dependencies in tests.
Plugin Architecture
Where we needed to modify core Magento behavior (pricing, product salability), we used plugins (interceptors), never preferences. This avoids class rewrites and plays nicely with other extensions.
Encrypted Credentials
API keys are stored using Magento’s EncryptorInterface and only decrypted at the point of use. No plaintext secrets in config or database.
What ForgeSync Does
The module manages bidirectional data synchronization between Magento and Sage 100 ERP across a wide surface area.
Inbound (ERP → Magento)
Scheduled imports pull data from the Sage API and persist it to dedicated fs_-prefixed tables:
- Customers & Addresses: full account sync with division/customer number mapping
- Items & Inventory: product catalog with real-time stock levels
- Price Codes: the foundation of B2B pricing
- Sales Orders & Lines: order history visible to customers on the frontend
- Invoices, Lines & Tracking: complete invoice history with shipment tracking
- Warehouses & Shipping Methods: operational data used in order routing
In total, 422,000+ records across 15 InnoDB tables, imported on a 2-hour cron cycle.
Outbound (Magento → ERP)
When a customer places an order, ForgeSync enqueues it for export. A separate cron (every 5 minutes) batches pending orders and pushes them to Sage via the API. The export handles:
- Customer resolution: matching Magento customers to Sage accounts via order attributes, customer EAV attributes, or configurable defaults
- Address mapping: bill-to and ship-to with full field translation
- Line items: SKU, quantity, pricing, with unit-of-measure handling
- Shipping method mapping: a dedicated
fs_shipping_methodstable maps Magento carrier/method codes to Sage shipping codes - Tax schedule cascade: customer-level → ship-to-level → config default
CLI Interface
Every operation is available from the command line:
# Run all imports
php bin/magento forgesync:import
# Run a single import job
php bin/magento forgesync:import customers
# Export specific orders or enqueue by increment ID
php bin/magento forgesync:export --enqueue=100000042B2B Pricing Engine
The most complex piece of ForgeSync is the pricing engine, a PHP reimplementation of a Sage stored procedure (spPriceCalculation).
B2B pricing in Sage is not simple. ForgeSync evaluates a 6-level price code cascade to determine which pricing rule applies to a given customer/product combination:
- Customer + Item-specific price code
- Customer + Product Line price code
- Customer-level default price code
- Item-specific default
- Product Line default
- Global fallback
Each resolved price code can use one of 6 pricing methods:
| Code | Method |
|---|---|
| S | Standard Price (use base price as-is) |
| O | Override (flat override price) |
| C | Cost Plus (markup from cost) |
| P | Percent of List (discount from list) |
| M | Percent of Standard (discount from standard) |
| D | Discount (percentage off) |
On top of that, each method supports 5-tier quantity breaks. Buy more, pay less.
There’s also a promotional pricing override layer that can temporarily replace any calculated price during a date window.
The pricing engine hooks into Magento via a frontend-scoped observer on catalog_product_get_final_price. It’s frontend-only by design. Running ERP pricing in admin or cron contexts would cause session-related issues and unnecessary overhead.
We verified the PHP implementation against the original stored procedure with exact-match test cases across all pricing methods and quantity tiers.
Admin Experience
Legacy integrations often leave store operators flying blind. ForgeSync includes three admin grids built with Magento’s UI Component framework:
Export Queue
View pending, completed, and failed order exports with timestamps and error messages. Supports mass-export actions.
Import Dashboard
Monitor import jobs, trigger manual runs, and see record counts per entity type.
Shipping Methods
Inline-editable grid for mapping Magento carrier/method codes to Sage shipping codes.
All grids live under a dedicated ForgeSync menu with granular ACL resources (::queue, ::import, ::shipping_methods) for role-based access control.
Customer-Facing Features
ForgeSync extends the Magento customer account area with ERP data that B2B buyers actually need:
Invoice History
Searchable list of all Sage invoices with line-item detail and tracking numbers.
Sales Order History
Full ERP order history (not just Magento orders), with status and line details.
Pending Orders
View orders that are in-process on the ERP side.
Each controller enforces ownership verification, so customers can only see records matching their ar_division_no and customer_no. Search filters and configurable cutoff periods keep the interface manageable for accounts with years of transaction history.
Testing
ForgeSync includes a unit test suite covering the three most critical services:
- PriceCalculationTest: 21 tests covering all 6 pricing methods, quantity breaks, promo overrides, and edge cases
- ImportServiceTest: 10 tests for record processing, error handling, and idempotency
- OrderExportTest: 9 tests for customer resolution, address mapping, and payload construction
php vendor/bin/phpunit --bootstrap vendor/autoload.php \
app/code/Forgewise/ForgeSync/Test/Unit/Service/We also built a mock API server (Node.js/Express) that reads from staging data and validates HMAC-SHA256 authentication, allowing full end-to-end testing without touching the production ERP.
Technical Decisions Worth Noting
A few choices that proved important during development:
NULL handling in ERP data
Sage fields frequently contain NULL rather than empty strings. All queries against fs_* tables use IFNULL(col, '') = '' patterns via a sqlEmpty() helper to avoid subtle bugs.
Frontend-only observers
Pricing and salability observers are scoped to etc/frontend/events.xml. Running them globally would inject customer session dependencies into admin and cron contexts, which is a common source of hard-to-debug errors in Magento modules.
Customer EAV attributes
Rather than a separate customer table, ForgeSync stores ERP identifiers (division, customer number, warehouse code, permissions) as native Magento customer attributes. An idempotent data patch handles installation and safely coexists with attributes from the legacy module.
Order table extension
ERP-specific order fields (warehouse code, Sage PO number, shipping code, etc.) are added to sales_order via db_schema.xml with a properly generated whitelist. No setup scripts.
Results
ForgeSync replaced a fragile legacy integration with a module that is:
40 unit tests, mock API for integration testing
Admin grids for every sync operation
Service contracts, DI, plugins, no magic
422K+ records synced reliably on a 2-hour cycle
Clean interfaces make adding new entity types straightforward
The module handles the full lifecycle: importing master data, calculating complex B2B pricing in real time, exporting orders back to the ERP, and giving both admins and customers visibility into the process.
Related Insights
Navigating the 10,000+ SKU Challenge
How attribute-based filtering transformed product discovery for an electronics supply distributor.
Read MoreThe Hidden Costs of Poor Inventory Management
How tracking the right metrics can save you thousands in inventory costs and prevent stockouts.
Read MoreNeed a reliable ERP integration?
Whether you need a new integration built from scratch or your current one needs rescuing, we’d love to talk.
Get in Touch