PHPackages                             spryker-eco/punchout-gateway - 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. [API Development](/categories/api)
4. /
5. spryker-eco/punchout-gateway

ActiveLibrary[API Development](/categories/api)

spryker-eco/punchout-gateway
============================

PunchoutGateway module

1.2.0(3w ago)031.2k↑1078.4%1proprietaryPHP &gt;=8.3

Since Apr 22Compare

[ Source](https://github.com/spryker-eco/punchout-gateway)[ Packagist](https://packagist.org/packages/spryker-eco/punchout-gateway)[ RSS](/packages/spryker-eco-punchout-gateway/feed)WikiDiscussions Synced 3w ago

READMEChangelog (10)Dependencies (61)Versions (16)Used By (1)

PunchOut Gateway
================

[](#punchout-gateway)

[![Latest Stable Version](https://camo.githubusercontent.com/d6e65c246f6b7db3c4e390a605f51ca54c97cd51dd7cb3b7d13436b2b3276ef8/68747470733a2f2f706f7365722e707567782e6f72672f737072796b65722d65636f2f70756e63686f75742d676174657761792f762f737461626c652e737667)](https://packagist.org/packages/spryker-eco/punchout-gateway)[![Minimum PHP Version](https://camo.githubusercontent.com/9c50dc780fa576f5c39b4feff00c05345c1471be0808881a09e750b91220dc54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344253230382e332d3838393242462e737667)](https://php.net/)[![Documentation](https://camo.githubusercontent.com/7af0a35c79e858e9ab62c3b0a93d66016294f0dad4ba688725406057161ebb09/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f737072796b65722d646f63756d656e746174696f6e2d6379616e)](https://docs.spryker.com/docs/pbc/all/punchout-gateway/integrate-punchout-gateway)

The PunchOut Gateway module provides a basic implementation of OCI and cXML PunchOut flows for Spryker shops. It lets eProcurement systems log a buyer into the shop, build a cart, and transfer that cart back to the buyer's procurement system.

Supported use cases
-------------------

[](#supported-use-cases)

- Any number of simultaneously active OCI and cXML connections in a single shop.
- **OCI:** every login creates a fresh `Punchout` cart.
- **cXML:** a single cart is created or reused per `BuyerCookie` value, so a buyer can resume an in-progress cart.
- iframe embedding (global, opt-in — see [Support iframe embedding](#support-iframe-embedding)).

Architecture at a glance
------------------------

[](#architecture-at-a-glance)

A PunchOut **connection** (stored in `spy_punchout_connection`) defines one buyer integration: its protocol, store, processor plugin, and field mapping. Connections are managed in the Back Office UI. Each inbound request resolves a **processor plugin** at runtime by the FQCN stored on the connection. The flow:

1. Buyer's system authenticates (OCI credential or cXML shared secret).
2. The shop resolves a customer and a quote, logs the buyer in, and persists a `spy_punchout_session`.
3. Buyer shops; the **Transfer Cart** widget posts the cart back to the buyer's procurement system.

---

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

[](#installation)

### 1. Install the module

[](#1-install-the-module)

```
composer require spryker-eco/punchout-gateway:^1.0.0
```

### 2. Configure the module

[](#2-configure-the-module)

Optional logging via AWS Parameter Store.

`config/Shared/config_default.php`

```
use SprykerEco\Shared\PunchoutGateway\PunchoutGatewayConstants;

$config[PunchoutGatewayConstants::ENABLE_LOGGING] = getenv('PUNCHOUT_GATEWAY_ENABLE_LOGGING') ?: false;
```

ConstantDescriptionDefault`ENABLE_LOGGING`Enables or disables logging for PunchOut Gateway.`false`When enabled, the module emits structured entries through `PunchoutLoggerInterface` (request reception/parsing, authentication, response generation, quote/session creation, uncaught throwables). When disabled, the resolver returns `NullPunchoutLogger` and all calls are no-ops.

### 3. Additional module configuration

[](#3-additional-module-configuration)

`src/Pyz/Zed/PunchoutGateway/PunchoutGatewayConfig.php`:

MethodDefaultDescription`isLoggingEnabled()``false`Enables or disables PunchOut Gateway logging.`getCxmlSessionStartUrlValidityInSeconds()``600`Validity (s) of the cXML session start URL. Bounds 0–3600.`getOciDefaultStartUrl()``'/'`Default redirect URL after OCI session start.`getCxmlSessionTokenLength()``32`Length of the generated cXML session token. Bounds 16–128.These values can also be changed at runtime in the Back Office under *Configuration &gt; Punchout Gateway*.

### 4. Update Quote configuration

[](#4-update-quote-configuration)

Allow the PunchOut session field to be saved with the quote.

`src/Pyz/Zed/Quote/QuoteConfig.php`

```
use Generated\Shared\Transfer\QuoteTransfer;

public function getQuoteFieldsAllowedForSaving(): array
{
    return array_merge(parent::getQuoteFieldsAllowedForSaving(), [
        // ...
        QuoteTransfer::PUNCHOUT_SESSION,
    ]);
}
```

### 5. Set up the database schema

[](#5-set-up-the-database-schema)

```
vendor/bin/console propel:install
```

Creates:

TableDescription`spy_punchout_connection`PunchOut connection configuration per store.`spy_punchout_credential`Credentials (username/password) linked to a connection and customer.`spy_punchout_session`Active PunchOut sessions linked to a quote.### 6. Generate transfer objects

[](#6-generate-transfer-objects)

```
vendor/bin/console transfer:generate
```

### 7. Register plugins

[](#7-register-plugins)

**Quote expander** — `src/Pyz/Zed/Quote/QuoteDependencyProvider.php`

```
use SprykerEco\Zed\PunchoutGateway\Communication\Plugin\Quote\PunchoutSessionQuoteExpanderPlugin;

protected function getQuoteExpanderPlugins(): array
{
    return [
        // ...
        new PunchoutSessionQuoteExpanderPlugin(),
    ];
}
```

**Route provider** — `src/Pyz/Yves/Router/RouterDependencyProvider.php`

```
use SprykerEco\Yves\PunchoutGateway\Plugin\Router\PunchoutGatewayRouteProviderPlugin;

protected function getRouteProvider(): array
{
    return [
        // ...
        new PunchoutGatewayRouteProviderPlugin(),
    ];
}
```

**Security header expander** — `src/Pyz/Yves/Application/ApplicationDependencyProvider.php`

```
use SprykerEco\Yves\PunchoutGateway\Plugin\Application\PunchoutSecurityHeaderExpanderPlugin;

protected function getSecurityHeaderExpanderPlugins(): array
{
    return [
        // ...
        new PunchoutSecurityHeaderExpanderPlugin(),
    ];
}
```

#### Support iframe embedding

[](#support-iframe-embedding)

If your eProcurement system embeds the shop in an iframe, set this in the deploy file for each environment:

```
image:
  environment:
    SPRYKER_YVES_SESSION_COOKIE_SAMESITE: 'none'
```

iframe headers are emitted per connection via the **Allow iFrame** flag (or whenever a `~TARGET` form field is sent).

### 8. Register the cart widget

[](#8-register-the-cart-widget)

`src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php`

```
use SprykerEco\Yves\PunchoutGateway\Widget\PunchoutCartWidget;

protected function getGlobalWidgets(): array
{
    return [
        // ...
        PunchoutCartWidget::class,
    ];
}
```

Embed the widget in the cart template (stock `spryker-shop/cart-page`: `SprykerShop/Yves/CartPage/Theme/default/templates/page-layout-cart/page-layout-cart.twig`, or your override) so the **Transfer Cart** button shows on the cart page:

```
{% raw %}
{% widget 'PunchoutCartWidget' args [data.cart] only %}{% endwidget %}
{% endraw %}
```

### 9. Import glossary data

[](#9-import-glossary-data)

```
# Option 1 — module config file
vendor/bin/console data:import --config=vendor/spryker-eco/punchout-gateway/data/import/punchout-gateway.yml

# Option 2 — copy vendor/spryker-eco/punchout-gateway/data/import/*.csv into
# data/import/common/common/, then:
vendor/bin/console data:import glossary
```

Back Office Zed translations ship in `data/translation/Zed/en_US.csv` and `de_DE.csv` and are picked up by the standard Zed translator — no separate import. Override a label by adding the same key to your project's Zed translation file.

### Verify the integration

[](#verify-the-integration)

- Open *Punchout Connections* in the Back Office — the grid renders empty until you create a connection.
- Run the demo command and confirm `spy_punchout_connection` and the grid reflect the demo cXML and OCI connections for store `DE`:

```
vendor/bin/console punchout-gateway:demo-connection:create
```

Do not use demo data in production.

---

Manage connections (Back Office)
--------------------------------

[](#manage-connections-back-office)

Open *Punchout Connections*. The grid lists every connection across all stores with *View*, *Edit*, *Activate*/*Deactivate*, and *Delete* actions.

### Create a connection

[](#create-a-connection)

Common fields:

FieldNotes**Connection Name**Human-readable label. Required, up to 255 chars, not unique.**Store**Store the buyer must be logged in to.**Protocol Type**`oci` or `cxml`. Cannot be changed after creation.**Processor Plugin Class**FQCN of a processor plugin. The dropdown only offers plugins whose `getType()` matches the protocol.**Active**When unchecked, requests to this connection are rejected.**Allow iFrame**When checked, the Storefront emits iframe-friendly CSP headers while the session is active.Protocol-specific fields appear dynamically:

**cXML**

FieldNotes**Sender Identity**Must be unique. Matched against the buyer's `Header/Sender/Credential/Identity`.**Sender Shared Secret**Stored hashed (`password_hash()`); incoming `SharedSecret` verified with `password_verify()`.cXML request URL is fixed at `/punchout-cxml-setup`, optionally followed by a slug — post to `/punchout-cxml-setup/` to target a specific connection.

**OCI**

FieldNotes**Request URL**Slug appended to `/punchout-gateway/oci/`. Only `_`, `-`, letters, digits allowed.**Form Method**`POST` or `GET` — method the buyer uses to submit the login form.**Username Field Name**Form field carrying the username. Default `USERNAME`.**Password Field Name**Form field carrying the password. Default `PASSWORD`.### Edit / activate / delete

[](#edit--activate--delete)

On *Edit*, **Protocol Type** is read-only; for cXML the **Sender Shared Secret** is blank — leave blank to keep, type to rotate. Use *Activate*/*Deactivate* to toggle without opening the form. *Delete* cascades — it removes every credential and session of the connection, ending in-flight carts.

### Manage credentials (OCI only)

[](#manage-credentials-oci-only)

Credentials map a username/password pair to a Spryker customer. On the connection's *View* page, select *Add credential*:

FieldNotes**Username**Sent by the buyer in the `usernameField` form field.**Password** / **Repeat Password**Stored as `password_hash()`. Leave blank on edit to keep the existing hash.**Customer ID**The Spryker customer logged in on successful auth.**Active**When unchecked, the credential is rejected even on matching username/password.cXML connections need no credentials — the customer is identified by the `UserEmail` extrinsic. For both protocols, customers must be fully configured in the shop so that only permitted products and prices are accessible.

---

Field mapping
-------------

[](#field-mapping)

Each connection can override how individual outbound protocol fields are populated, without a custom processor plugin. Mappings are stored in the connection's `configuration` JSON under `mapping` and edited in the Back Office connection form (available only when editing, not creating).

`mapping` is an object of `targetField: sourceExpression` pairs. OCI example:

```
{
  "mapping": {
    "NEW_ITEM-DESCRIPTION": "item.name",
    "NEW_ITEM-VENDORMAT": "item.sku&\"_DE\"",
    "NEW_ITEM-LONGTEXT": "item.description"
  }
}
```

- **Target field** — for OCI a `NEW_ITEM-*` field; for cXML a full cXML path (e.g. `cXML.Message.PunchOutOrderMessage.ItemIn.ItemDetail.Description`) or a custom extrinsic.
- **Source expression** — where the value comes from at cart-return time. Required fields fall back to their default when unmapped; optional fields are emitted only when mapped.

### Source expression syntax

[](#source-expression-syntax)

FormExampleResultPlugin expression`item.sku`Value read from a field-mapper plugin keyed `item`, path `sku`.Quoted constant`"EA"` / `'EA'`The literal text.Concatenation`item.sku&"_suffix"`Segments joined with `&`, resolved individually then concatenated.Forced empty`""`An explicit empty value.Expressions are evaluated at cart-return time by `FieldValueResolver`. The Back Office source input is an autosuggest served by `/punchout-gateway/source-field-suggestions/index`.

### Field-mapper plugins

[](#field-mapper-plugins)

Source roots are provided by plugins implementing `PunchoutFieldMapperPluginInterface`, registered keyed by `pluginKey` in `Service\PunchoutGateway\PunchoutGatewayDependencyProvider::getFieldMapperPlugins()`. Shipped:

KeyPluginSource transfer`item``ItemTransferFieldMapperPlugin``ItemTransfer` of the current cart item.`quote``QuoteTransferFieldMapperPlugin``QuoteTransfer` of the cart.To add a root (e.g. `company`), implement the interface and register it under a new key:

`src/Pyz/Service/PunchoutGateway/PunchoutGatewayDependencyProvider.php`

```
namespace Pyz\Service\PunchoutGateway;

use Pyz\Service\PunchoutGateway\Plugin\FieldMapper\CompanyTransferFieldMapperPlugin;
use SprykerEco\Service\PunchoutGateway\PunchoutGatewayDependencyProvider as SprykerEcoPunchoutGatewayDependencyProvider;

class PunchoutGatewayDependencyProvider extends SprykerEcoPunchoutGatewayDependencyProvider
{
    protected function getFieldMapperPlugins(): array
    {
        return [
            ...parent::getFieldMapperPlugins(),
            'company' => new CompanyTransferFieldMapperPlugin(),
        ];
    }
}
```

### Custom extrinsics (cXML)

[](#custom-extrinsics-cxml)

You can map values to custom `Extrinsic` elements in each `ItemIn`. The name must match `^[A-Za-z0-9_]+$` and must not be a reserved buyer-identity name: `User`, `UniqueUsername`, `UniqueName`, `UserId`, `UserEmail`, `UserFullName`, `UserPrintableName`, `FirstName`, `LastName`, `PhoneNumber`, `UserPhoneNumber`. These are also stripped from echoed extrinsics to avoid leaking PII.

---

Endpoints
---------

[](#endpoints)

`PunchoutGatewayRouteProviderPlugin` registers:

MethodPathPurpose`POST``/punchout-cxml-setup`Inbound cXML `PunchOutSetupRequest`.`GET``/punchout-cxml-start?session={token}`Buyer's browser opens this with the token from the synchronous `PunchOutSetupResponse`.`POST``/punchout-gateway/oci/{connectionSlug}`Inbound OCI login form (default method).`GET``/punchout-gateway/oci/{connectionSlug}`Inbound OCI login when the connection uses `formMethod=GET`.The OCI slug matches `[a-zA-Z0-9_-]+`.

### cXML session start lifecycle

[](#cxml-session-start-lifecycle)

Two-step handshake:

1. Buyer `POST`s a `PunchOutSetupRequest`. The shop authenticates, resolves customer and quote, persists a `spy_punchout_session`, and replies synchronously with a `PunchOutSetupResponse` whose `StartPage/URL` carries a one-shot token: `https:///punchout-cxml-start?session=`.
2. Buyer's browser follows that URL. `CxmlController::startAction` reads the token, looks up the session, logs the customer in, persists the protocol CSP fragment, and redirects to the shop.

---

Protocol coverage
-----------------

[](#protocol-coverage)

cXML and OCI cover a broad range of features; the table below summarizes what the default flow parses and emits. Elements not listed are not interpreted by the default flow (cXML inbound extras are collected into `extrinsicFields`; OCI inbound extras are preserved in `PunchoutOciLoginRequestTransfer.formData`). Full field-by-field mapping: [PunchOut Protocols Coverage](https://docs.spryker.com/docs/pbc/all/punchout-gateway/punchout-protocol-coverage.html).

### cXML — received (buyer → Spryker)

[](#cxml--received-buyer--spryker)

`PunchOutSetupRequest` is parsed by `DefaultCxmlContentParser` into `PunchoutCxmlSetupRequestTransfer`. Key elements:

- **Header:** `@payloadID`, `@timestamp`, `From/To/Sender Credential/Identity`, `Sender/Credential/SharedSecret` (verified against the connection hash).
- **Payload:** `@operation` (`create`/`edit`), `BuyerCookie`, `BrowserFormPost/URL`, `Extrinsic` (collected as key/value map).
- **ShipTo/Address** and, only for `operation="edit"`, the `ItemOut` list (mapped to `PunchoutItemTransfer`).

### cXML — returned (Spryker → buyer)

[](#cxml--returned-spryker--buyer)

- `PunchOutSetupResponse` — synchronous reply (HTTP 200, `text/xml`) with `StartPage/URL` carrying the session token. On error, a `Status` document with a non-200 code is returned instead.
- `PunchOutOrderMessage` — cart return, POSTed to `BrowserFormPost.URL`. Per item it emits SKU, quantity, name, unit price, `EA` unit of measure, classification, and optional fields when mapped. Header carries `BuyerCookie`, totals (currency/shipping/tax), and `ShipTo` address. Extrinsics are echoed back with the deny-list keys removed.

### OCI — received (buyer → Spryker)

[](#oci--received-buyer--spryker)

FieldRequiredPurpose`USERNAME`YesBuyer user. Field name configurable via `usernameField`. Matched against `spy_punchout_credential.username`.`PASSWORD`YesAuthenticates the buyer. Field name configurable via `passwordField`. Verified against the stored hash.`HOOK_URL`YesCart-return target. Must start with `https://`. Stored as `browserFormPostUrl`.`~TARGET`NoFrame target echoed back to the buyer.`~OkCode`, `~CALLER`NoSAP control fields.### OCI — returned (Spryker → buyer)

[](#oci--returned-spryker--buyer)

A `Transfer Cart` HTML form whose `action` is the received `HOOK_URL` (and `target` is `~TARGET`). Per item: `NEW_ITEM-DESCRIPTION[N]` (name), `NEW_ITEM-QUANTITY[N]`, `NEW_ITEM-UNIT[N]` (`EA`), `NEW_ITEM-PRICE[N]` (unit price), `NEW_ITEM-CURRENCY[N]`, `NEW_ITEM-VENDORMAT[N]` (SKU). `~OkCode`/`~CALLER` are echoed when present. Any `NEW_ITEM-*` source is overridable per connection via field mapping.

---

Extension points
----------------

[](#extension-points)

### Processor plugin

[](#processor-plugin)

Each connection resolves its processor plugin at runtime by the FQCN in `spy_punchout_connection.processor_plugin_class`. No DI registration required. Implement:

- OCI — `PunchoutProcessorPluginInterface`
- cXML — `PunchoutCxmlProcessorPluginInterface`

Defaults: `DefaultOciProcessorPlugin`, `DefaultCxmlProcessorPlugin`. The simplest customization is to extend a default and override only what you need, then point the connection's `processor_plugin_class` at your class:

```
namespace Pyz\Zed\ProjectPunchoutGateway\Communication\Plugin\PunchoutGateway;

use SprykerEco\Zed\PunchoutGateway\Communication\Plugin\PunchoutGateway\DefaultOciProcessorPlugin;

class CustomOciProcessorPlugin extends DefaultOciProcessorPlugin
{
    // Override only the methods you need.
}
```

Lifecycle methods:

MethodCalled whenFunctionality`getType`At plugin resolutionReports the protocol (`oci`/`cxml`) this plugin handles.`authenticate`First login stepFinds a connection from the setup request; `null` if none.`resolveCustomer`After connection foundFinds the customer; `null` if none.`resolveQuote`After customer resolvedCreates or reuses a quote.`expandQuote`After quote resolvedAdjusts the quote post-PunchOut prep.`resolveSession`Before session persistedAdditional session validation.`parseCxmlRequest` *(cXML)*After XML parsed, connection foundExtra mapping of cXML data onto the setup request.`expandResponse` *(cXML)*After session creationExpands the login response (e.g. default start URL).### Default behavior (out of the box)

[](#default-behavior-out-of-the-box)

Both default plugins use `QuoteCreator` to stamp **Store** (from `fk_store`) and **Currency** (store default) on every new quote.

- **OCI** — customer comes entirely from the matched credential record (`PunchoutOciAuthenticator` → `connection.idCustomer`); every login starts a fresh empty cart; items are not carried on login — they travel back via the Transfer Cart POST.
- **cXML** — customer resolved from the `UserEmail` extrinsic; quote reused per `BuyerCookie` (recreated if it belongs to a different store); items are mapped from `ItemOut` on `operation="edit"`.

### Form handler plugin

[](#form-handler-plugin)

The Transfer Cart form is built by `PunchoutFormHandlerPluginInterface` implementations; `PunchoutFormDataBuilder::build()` returns the first whose `isApplicable()` is `true`. Defaults: `DefaultOciPunchoutFormHandlerPlugin`, `DefaultCxmlPunchoutFormHandlerPlugin`. Register a custom handler **before** the defaults so it takes precedence for its protocol:

`src/Pyz/Yves/PunchoutGateway/PunchoutGatewayDependencyProvider.php`

```
namespace Pyz\Yves\PunchoutGateway;

use Pyz\Yves\ProjectPunchoutGateway\Plugin\Form\CustomCxmlPunchoutFormHandlerPlugin;
use SprykerEco\Yves\PunchoutGateway\PunchoutGatewayDependencyProvider as SprykerEcoPunchoutGatewayDependencyProvider;

class PunchoutGatewayDependencyProvider extends SprykerEcoPunchoutGatewayDependencyProvider
{
    protected function getPunchoutFormHandlerPlugins(): array
    {
        return [
            new CustomCxmlPunchoutFormHandlerPlugin(),
            ...parent::getPunchoutFormHandlerPlugins(),
        ];
    }
}
```

The Twig template renders the form only when `formData` is non-null and `actionUrl` is non-empty. For OCI the button shows even for an empty cart (to allow empty-order return); for cXML no button renders for an empty cart (to be improved).

### Security header expander plugin

[](#security-header-expander-plugin)

At session start, Yves applies protocol-specific CSP directives via `PunchoutSecurityHeaderExpanderPluginInterface` implementations so the Storefront can be embedded and post back. Default: `DefaultOciSecurityHeaderExpanderPlugin` (adds `frame-ancestors` for OCI sessions carrying `~TARGET`). CSP headers are included when `allow_iframe` is `true`. Register a custom expander via `getPunchoutSecurityHeaderExpanderPlugins()`.

### Session-in-quote expander plugin

[](#session-in-quote-expander-plugin)

`PunchoutQuoteExpander` runs the session through every registered `PunchoutSessionInQuoteExpanderPluginInterface` before stamping it on the `QuoteTransfer`. Use it to enrich or override session fields based on the quote before it is persisted with the cart.

---

Documentation
-------------

[](#documentation)

- [Integrate PunchOut Gateway](https://docs.spryker.com/docs/pbc/all/punchout-gateway/integrate-punchout-gateway.html)
- [Manage PunchOut connections](https://docs.spryker.com/docs/pbc/all/punchout-gateway/manage-punchout-connections.html)
- [Project configuration for PunchOut Gateway](https://docs.spryker.com/docs/pbc/all/punchout-gateway/project-configuration-for-punchout-gateway.html)
- [PunchOut Protocols Coverage](https://docs.spryker.com/docs/pbc/all/punchout-gateway/punchout-protocol-coverage.html)

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance94

Actively maintained with recent releases

Popularity30

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

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 ~5 days

Total

13

Last Release

26d ago

Major Versions

0.5.1 → 1.0.02026-06-16

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/10738957?v=4)[Spryker Bot](/maintainers/spryker-bot)[@spryker-bot](https://github.com/spryker-bot)

### Embed Badge

![Health badge](/badges/spryker-eco-punchout-gateway/health.svg)

```
[![Health](https://phpackages.com/badges/spryker-eco-punchout-gateway/health.svg)](https://phpackages.com/packages/spryker-eco-punchout-gateway)
```

###  Alternatives

[spryker/search

Search module

152.9M84](/packages/spryker-search)

PHPackages © 2026

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