Skip to main content

PunchOutGatewayPlugin

A plugin that integrates Vendure with PunchCommerce to enable PunchOut/cXML procurement gateway functionality. This allows procurement systems to redirect users to your Vendure storefront, where they can browse and add items to a cart, then transfer the cart back to the procurement system.

How It Works

  1. Buyer clicks PunchOut link in their ERP → PunchCommerce redirects to your storefront with sID and uID query params
  2. Storefront authenticates the buyer by calling Vendure's authenticate mutation with the punchout strategy
  3. Buyer shops normally — all order mutations use activeOrderInput to scope the cart to the PunchOut session
  4. On checkout, storefront calls transferPunchOutCart(sID) to send the cart back to PunchCommerce

Installation

Configuration

Ts

Options

OptionRequiredDefaultDescription
apiUrlNohttps://www.punchcommerce.deBase URL of the PunchCommerce gateway. Override for staging or self-hosted instances.
shippingCostModeNo'nonZero'Controls shipping line item in the basket: 'all' = always include, 'nonZero' = only when > 0, 'none' = never include.
productFieldMappingNoMaps PunchCommerce product fields to static values or ProductVariant custom field names. See below.

Product Field Mapping

By default, all products are sent as pieces (unit: 'PCE'). If your catalog includes products with different units (weight, volume, etc.), you can map PunchCommerce fields to ProductVariant custom fields or static values.

Each field accepts either a static value or a { customField, default } object that reads from the variant at transfer time:

Ts

Available fields:

FieldDefaultDescription
unit'PCE'OCI unit code (e.g. 'PCE', 'KG', 'LTR')
unit_name'Piece'Human-readable unit name
packaging_unit'Piece'Packaging unit description
purchase_unit1Purchase unit quantity
reference_unit1Reference unit quantity
weight0Product weight

Customer Setup

Customers are linked to PunchCommerce via a custom field on the Customer entity.

  1. In PunchCommerce: create a customer and set the "Customer identification" (this becomes the uID)
  2. In Vendure admin: open the customer, set the "PunchOut Customer ID (uID)" custom field to the same value

PunchCommerce Configuration

In the PunchCommerce dashboard, configure your customer:

  • Entry address: your storefront's PunchOut landing page URL (e.g. https://my-store.com/punchout)
  • Customer identification: a unique identifier matching the Vendure customer's custom field

PunchCommerce will redirect buyers to your Entry address with ?sID={UUID}&uID={identifier} appended.

Storefront Requirements

Since Vendure is headless, your storefront must handle the PunchOut flow. A full working example is available at vendurehq/punchcommerce-storefront-demo.

Here's what needs to be implemented:

1. PunchOut Landing Page

Create a route (e.g. /punchout) that PunchCommerce redirects to. This page must:

  1. Extract sID and uID from the query params
  2. Store the sID for the duration of the session (e.g. in sessionStorage)
  3. Call the authenticate mutation
  4. Redirect to the shop homepage on success
Ts

2. Session-Scoped Cart (activeOrderInput)

All order operations (queries and mutations) must include activeOrderInput: { punchout: { sID } } to scope the cart to the PunchOut session. This enables parallel sessions for the same customer.

Ts

Pass activeOrderInput on all order operations: activeOrder, addItemToOrder, adjustOrderLine, removeOrderLine, setOrderShippingAddress, setOrderShippingMethod, eligibleShippingMethods, etc.

To display the cart, query activeOrder with the same input:

Ts

3. Transfer Cart (replaces Checkout)

Replace the normal checkout flow with a "Transfer Cart" / "Back to Procurement" button that sends the cart to PunchCommerce:

Ts

4. iFrame Support (if applicable)

If PunchCommerce is configured for iFrame PunchOut (embedding the shop inside the ERP), your storefront must:

  • Set SameSite=None; Secure on all session cookies
  • Remove the X-Frame-Options header during PunchOut sessions
  • These are typically configured in your web server or storefront framework

GraphQL API Reference

Authentication (built-in mutation)

Graphql

Transfer Cart

Graphql

Requires an authenticated PunchOut session.

Cart Mapping

The plugin maps Vendure order lines to PunchCommerce basket positions:

  • Prices use gross/net pattern: price = gross (with tax), price_net = net (without tax)
  • All monetary values are converted from Vendure's integer cents to decimal (÷ 100)
  • Shipping is included as a separate position with type: 'shipping-costs' (controlled by shippingCostMode)
  • Product descriptions: description is plain text (HTML stripped), description_long preserves HTML
  • Basket is sent as multipart/form-data to PunchCommerce's /gateway/v3/return endpoint

Order Lifecycle

After a successful cart transfer, the order transitions to a custom Transferred state:

AddingItems → Transferred
  • The order becomes inactive (active = false), so a new PunchOut session creates a fresh cart
  • The order and all its line items are preserved in Vendure for record-keeping
  • The order is visible in the Vendure admin under Orders with state Transferred
  • Re-transferring the same session returns an error since no active order exists

The actual purchase order (PO) comes later through a separate channel — either manually or via cXML order transmission (future scope). The Transferred state represents "cart handed off to procurement system, awaiting PO."

Parallel Sessions

The plugin uses a custom ActiveOrderStrategy to scope orders by PunchOut session ID (sID). At the API level:

  • Each PunchOut session gets its own empty cart
  • The same customer can have multiple concurrent PunchOut sessions
  • Carts are isolated — items added in one session don't appear in another

Storefront considerations

Browser cookies are scoped per-domain, not per-tab. If your storefront stores the sID in a cookie, only one PunchOut session can be active at a time — starting a new session overwrites the cookie and the previous session's cart becomes inaccessible from the UI.

To support truly parallel sessions, store the sID in sessionStorage (which is tab-scoped) and pass it explicitly to server actions. This way each browser tab/iframe maintains its own independent PunchOut session.

When a new PunchOut session starts and replaces the previous sID, make sure to revalidate any cached cart data so the UI reflects the new (empty) cart instead of showing stale items from the previous session.

Signature
Was this chapter helpful?
Report Issue
Edited Apr 3, 2026·Edit this page