ibrand/open-ecommerce
ibrand/open-ecommerce is a modular Laravel e-commerce package for building customizable online stores. It provides product catalog, cart, checkout, order, payment, and promotion components with an extensible admin backend, helping PHP developers launch and extend commerce applications quickly.
Assumptions to verify first: this package sits at the API layer of the iBrand e-commerce stack — it is the Open API (routes + controllers + resources) over a set of sibling packages (ibrand/laravel-shopping-cart, address, settings, repository, tag). It is not a batteries-included storefront. v2.0.3 also ships an admin-side mall settings + micro-page (微页面) editor whose output is consumed by a WeChat mini-program client, so treat this as "storefront API + lightweight CMS" rather than a pure headless catalog. Read composer.json and routes/ before anything else to confirm which Laravel and which ibrand/* versions it pins — v2.0.3 explicitly bumped third-party packages, so do not carry over assumptions from 1.x.
Minimal bring-up:
git clone https://github.com/guojiangclub/ecommerce-open-api
composer install
cp .env.example .env && php artisan key:generate
# configure DB in .env, then:
php artisan migrate --seed
php artisan passport:install # OAuth2 clients for the API
php artisan vendor:publish --all # settings + micro-page assets/migrations, check --tag
php artisan serve
Where to look, in this order:
| Path | Why |
|---|---|
routes/ (usually api.php / routes/v1) |
The actual product surface — every endpoint is a feature |
config/ (ecommerce.php, ibrand.php) |
Currency, payment drivers, image/upload, order expiry |
src/Contracts + src/Repositories |
The seams you are meant to override |
database/migrations + seeders |
The real schema; treat it as the spec |
src/Http/Controllers/Admin + micro-page module registry |
New in 2.0.3 — the module catalogue (幻灯片/优惠券/快捷导航/魔方) and how pages are composed |
tests/ |
Often the only working documentation of the checkout flow |
First use case to target: read-only catalog → cart → order. Hit GET /api/products unauthenticated, then obtain a token (POST /oauth/token, password grant), add to cart, and place one order end-to-end with a cash/fake payment driver. Then repeat via the new direct-buy path (商品支持直接下单) — the two flows should converge on the same order-creation service; if they don't, that divergence is the first thing to fix. If both paths work, the rest of the domain is mostly variations. Do not start by reading models — start by tracing one request from route → controller → repository → model.
Use it as a service, not a library, if you can. Running it as a separate API process (its own DB or a read replica) and talking to it over HTTP keeps the upgrade blast radius contained. If you need it in-process, pin the exact commit/tag in composer.json and vendor it under a namespace you control. The 2.x line is still moving (2.0.3 added schema-bearing features), so an unpinned dev-master is a guaranteed surprise.
Override through the container, not by editing models. The packages are repository-driven; the intended extension point is binding your own implementation to an ibrand.* contract in a service provider:
// app/Providers/CommerceServiceProvider.php
$this->app->bind(
\iBrand\EC\Open\Core\Contracts\OrderContract::class,
\App\Commerce\Orders\OrderRepository::class
);
Confirm the exact FQCNs from src/Contracts — they moved between releases and 2.0.3 is a minor release on top of a major, so re-check after upgrading. Keep your overrides in app/, never in vendor/, so a composer update on a fork stays mergeable.
Treat order placement as one transaction with explicit locking. Stock decrement plus cart conversion is the classic race here, and the new direct buy path adds a second entry point into it. Funnel both through one service:
DB::transaction(function () use ($items, $payload) {
$locked = $this->stock->lockForUpdate($items); // re-validate price + stock from DB
$order = $this->orders->create($payload, $locked); // never trust the cart/request snapshot
event(new OrderPlaced($order));
});
The cart is a quote, not a reservation — re-price server-side at submit time. If direct-buy reuses the cart table internally, confirm it uses a distinct/disposable cart so a "buy now" doesn't silently mutate the user's saved cart. This is where most integrations with this package go wrong.
Model the micro-page as a versioned document, not rows. 2.0.3 stores page composition as module blocks (slideshow, coupon, quick-nav, cube grid). You will get JSON-ish structures whose shape is only implied by the admin editor. Snapshot the schema, put a schema_version on it, and render it server-side into a stable, deliberately-shaped payload for your mini-program rather than dumping the stored blob. When the package adds a module, you add a renderer — you don't touch the client.
Add your own module via the registry, not by patching the editor. Find the module map/config that enumerates the block types and register a new class implementing the same render + admin-form surface. That keeps you on the supported extension path; editing the admin controllers is a permanent fork.
Drive post-order work off events, not controller edits. Order paid / shipped / cancelled events are the stable seam for email, inventory sync, ERP push, and analytics. A listener keeps you off the update treadmill — especially relevant given 2.0.3's "支付问题修复", which likely touched the paid transition.
Write a thin anti-corruption layer on the client side. The API returns framework-shaped payloads (Laravel resources over Eloquent). Map them into your own DTOs immediately so a schema drift in the package is one file to fix, not fifty. This applies doubly to micro-page payloads.
Money: check whether the schema stores integer minor units or decimal. If decimal, cast to integer cents at your boundary and never do arithmetic on the API's floats. Same for currency — there is a single configured currency, so multi-currency means a schema change, not a config change.
Testing: use the package's own factories for fixtures, but write your integration tests against the HTTP surface ($this->postJson('/api/cart', ...), $this->postJson('/api/orders/buy-now', ...)) with a fresh migrated DB per test. Mock the payment driver rather than the repository — the driver boundary is narrower and more stable. Cover cart-checkout and direct-buy with the same assertions; that's your regression net for the duplicated-logic risk.
composer.json constraints before planning. The 1.x-era facts (Laravel 5.7/5.8, str_slug, array_get, Passport-not-Sanctum, possibly Dingo) are worth re-verifying line by line; if 2.x genuinely modernised, your port budget shrinks a lot, and if it didn't, nothing changed. Upgrading 1.x → 2.x is a major-version jump: expect migrations, config keys, and contract FQCNs to have moved, and treat it as a port with a test plan, not a composer update.down() was previously broken, earlier migrations may be squashed or effectively irreversible. Validate migrate:refresh and RefreshDatabase in CI early; don't discover this mid-sprint.Dependents: 0 still means no ecosystem, no Stack Overflow answers, and no CVE triage on your behalf. Fork it, own the security patches, and note the exit cost in your ADR. Chinese-language issues and docs remain the only support channel.json_decode straight into your client.php artisan route:list is your map. Turn on DB::listen / APP_DEBUG and log the query log — repository layers hide N+1s badly, and product listing and micro-page assembly endpoints are the usual culprits. If it uses Dingo, API_DEBUG=true and API_STRICT=true in .env will surface the real exception instead of a generic JSON error.--tag options on vendor:publish. Seeding is required (settings, payment methods, shipping rules, mall settings) or checkout silently fails at the shipping step; micro-pages likewise have no content until seeded, which reads as "broken editor".APP_TIMEZONE and currency explicitly before seeding; order timestamps and price formatting are baked into seeded settings rows, and changing them later means data migration. 2.0.3's new mall-settings surface widens what gets baked in — set it before you seed, not after.How can I help you explore Laravel packages today?