mar-pod-b2b/module-b2b-core

Shared contracts and extension infrastructure for Marpod B2B modules.

Maintainers

Package info

gitlab.com/mar-pod-b2b/module-b2b-core

Issues

Type:magento2-module

pkg:composer/mar-pod-b2b/module-b2b-core

Transparency log

Statistics

Installs: 1

Dependents: 17

Suggesters: 0

Stars: 0

1.0.3 2026-08-21 13:36 UTC

README

Shared contracts and extension infrastructure for independently installable Marpod B2B modules.

The package belongs to the mandatory B2B Base product. It intentionally contains no company persistence, pricing rules, quote workflow, payment method, or storefront-private data.

Features

  • capability registry composed through Magento dependency injection;
  • B2B actor, company, website, and currency context contract;
  • versioned pricing-context contract for guest, customer, and company scopes;
  • deny-by-default authorization service backed by optional permission providers;
  • audit event and audit logger contracts with a safe no-op default;
  • bulk-cart request and per-item result contracts shared by Quick Order, CSV upload, and requisition lists;
  • independent B2B Admin menu (pinned directly under Dashboard), configuration section, and ACL resources;
  • a shared MarpodB2bBulkCartItemResult GraphQL type (etc/schema.graphqls) — declared here, not by any single feature module, since Purchase Approval's and Requisition List's convertToCart/addToCart mutations both return this shape and GraphQL types can't be declared twice across modules.

Requirements

  • PHP 8.5
  • Magento Open Source or Adobe Commerce 2.4.9
  • Magento Backend and Config modules

Installation

Run from the Magento project root:

composer require mar-pod-b2b/module-b2b-core:^1.0@dev
bin/magento module:enable Marpod_B2bCore
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:clean

During development, branch 1.x is exposed as 1.0.x-dev through the Composer branch alias.

Capability registration

An optional module registers its technical availability through DI:

<type name="Marpod\B2bCore\Model\CapabilityRegistry">
    <arguments>
        <argument name="capabilities" xsi:type="array">
            <item name="quick_order" xsi:type="boolean">true</item>
        </argument>
    </arguments>
</type>

Use CapabilityRegistryInterface::isAvailable() only to check whether code is installed. Configuration and user authorization are separate decisions.

Authorization providers

Feature modules add narrow PermissionProviderInterface implementations to the authorization pool:

<type name="Marpod\B2bCore\Model\Authorization\AuthorizationService">
    <arguments>
        <argument name="permissionProviders" xsi:type="array">
            <item name="company" xsi:type="object">Vendor\Module\Model\CompanyPermissionProvider</item>
        </argument>
    </arguments>
</type>

Unsupported permissions and an empty provider pool are denied. Mutating application services must perform authorization themselves; a controller check alone is insufficient.

Pricing context

PricingContextInterface explicitly identifies the pricing scope and keeps the acting customer separate from the company whose contract price is used. Supported scope types are:

  • guest with no scope ID;
  • customer with the actor customer ID;
  • company with the company ID.

The context also contains website and ISO 4217 currency codes so consumers can build safe cache keys without leaking company-specific data through FPC or Varnish.

Audit

The default NullAuditLogger prevents optional audit storage from becoming an availability dependency. A persistence module should replace AuditLoggerInterface and store only allow-listed metadata. Secrets, tokens, payment details, and unnecessary customer data must never be placed in audit metadata.

Tests

From a Magento project where this package is installed as a path repository:

vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist \
    packages/module-b2b-core/Test/Unit

vendor/bin/phpunit -c dev/tests/integration/phpunit.xml.dist \
    --bootstrap packages/module-b2b-core/Test/Integration/bootstrap.php \
    packages/module-b2b-core/Test/Integration

Also run Composer validation, PHP syntax checks, Magento coding standards, bin/magento setup:upgrade, and bin/magento setup:di:compile before release.

Shared API test infrastructure

Test/Api provides base classes any B2B module can extend for REST/GraphQL integration tests under dev/tests/api-functional (real HTTP calls against a running instance — routing, authentication, and ACL enforcement all execute for real, unlike a plain service-layer test):

  • Marpod\B2bCore\Test\Api\AbstractRestTestCase (extends WebapiAbstract) — getAdminToken() for the suite's configured full-access admin account; getRestrictedAdminToken(array $allowedAclResources), a throwaway role+user scoped to exactly the given ACL resources, for proving a route rejects a token that lacks them; restApiCall($path, $httpMethod, $token, $arguments) to make the call. Both admin fixtures are cleaned up in tearDown()always delete the role along with the user if you ever bypass this helper and create one directly: an orphaned authorization_role row breaks Magento\Framework\Acl\Builder for the entire site, not just the test suite, until it's removed.
  • Marpod\B2bCore\Test\Api\AbstractGraphQlTestCase (extends GraphQlAbstract) — getCustomerAuthHeaders($email, $password) for an authenticated customer GraphQL call.

Company-scoped fixture building (a company, and optionally a customer assigned to it) lives in Marpod\B2bCompany\Test\Api\CompanyFixtureTrait — any module that already depends on mar-pod-b2b/module-b2b-company (nearly all of them) can use it directly. createCustomerFixtureWithPermissions() on the same trait creates a customer whose role carries exactly the given CompanyPermissionInterface codes, for tests exercising a specific permission rather than plain company membership.

Run with the project's local (non-.dist) dev/tests/api-functional/phpunit_rest.xml / phpunit_graphql.xml, e.g.:

vendor/bin/phpunit -c dev/tests/api-functional/phpunit_rest.xml \
    packages/module-b2b-company-credit/Test/Api/Rest/CreditAccountRestTest.php

vendor/bin/phpunit -c dev/tests/api-functional/phpunit_graphql.xml \
    packages/module-b2b-company-credit/Test/Api/GraphQl/CompanyCreditGraphQlTest.php

Two environment quirks worth knowing before adding more tests here:

  • ACL-insufficient tokens get HTTP 401, not 403. Magento's webapi framework maps both "no token" and "token lacks the resource" to 401 — distinguish the latter by asserting the response body names the missing resource, not by status code alone.
  • Every Api\Data\*Interface getter/setter needs a real docblock with an explicit @return on its own line. Three patterns are silently misparsed by Magento's webapi reflection: no docblock at all ("Each method must have a doc block"), a single-line /** @param int $x @return $this */ combining both tags, and a single-line docblock with description text before the tag (e.g. /** Get X. @return int */). Only a bare single-tag one-liner (/** @return int */) or a full multi-line docblock (each tag on its own line) survives — these bugs surface only when something actually serializes that type through webapi/GraphQL, which is exactly what these tests do for the first time for several interfaces.
  • GraphQL mutations must be called with graphQlMutation(), not graphQlQuery(), in AbstractGraphQlTestCase-based tests — graphQlQuery() sends a GET, and Magento rejects any mutation operation sent that way ("Mutation requests allowed only for POST requests") regardless of the query string's own contents. A plain read query works fine through either method, so this only bites the first time a test file adds its first mutation call.

Cache and operations

The module creates no public cache entries, indexers, cron jobs, queues, or database tables. Feature modules remain responsible for precise cache tagging and invalidation of their own data.

Limitations

  • permission providers and persistent audit storage are supplied by feature modules;
  • this package does not resolve the current company or pricing context from a Magento session;
  • bulk-cart contracts do not implement product loading, inventory validation, or quote mutation.

Uninstallation

Disable and remove dependent Marpod B2B modules first:

bin/magento module:disable Marpod_B2bCore
composer remove mar-pod-b2b/module-b2b-core
bin/magento setup:upgrade
bin/magento cache:clean

The module owns no database tables, so uninstalling it does not delete business data. Composer prevents removal while another installed package requires it.