@charingcross/procuremesh-client
v1.1.0
Published
ProcureMesh browser client for punchout storefront integrations
Downloads
88
Maintainers
Readme
@charingcross/procuremesh-client
ProcureMesh browser client for punchout storefront integrations.
This project is proprietary and distributed under the terms in LICENSE.md.
Use it when your storefront needs to hand the current cart back to a buyer procurement system.
The library handles the punchout session lifecycle for you:
- detects
pm_tokenin the page URL - starts the ProcureMesh session automatically
- removes
pm_tokenfrom the browser URL - stores the token for the current browser session
- performs the gateway handshake
- transfers the cart back to procurement when checkout is triggered
The automatic punchout flow starts when the storefront is opened with a pm_token query parameter.
In practice, your integration only needs to:
- load the library with npm or a script tag
- provide your ProcureMesh gateway
url - provide
checkoutButtonSelector - return the current cart from
onCheckout
Install
npm
npm install @charingcross/procuremesh-clientimport { ProcureMesh } from "@charingcross/procuremesh-client";CDN
You can use the UMD bundle with a script tag.
jsDelivr example:
<script
src="https://cdn.jsdelivr.net/npm/@charingcross/[email protected]/dist/procuremesh-client.umd.js"
defer
></script>For production, pin the CDN URL to a package version so upgrades are explicit.
- Exact version:
@1.1.0 - Minor range:
@1.1 - Major range:
@1
Example versioned URL:
<script
src="https://cdn.jsdelivr.net/npm/@charingcross/[email protected]/dist/procuremesh-client.umd.js"
defer
></script>Self-hosted example:
<script
src="https://your-assets.example.com/procuremesh-client.umd.js"
defer
></script>You can either use jsDelivr or serve dist/procuremesh-client.umd.js from your own gateway, asset CDN, or storefront static files.
When loaded from a script tag, the library is available as window.ProcureMesh.
Quick Start
<script
src="https://cdn.jsdelivr.net/npm/@charingcross/[email protected]/dist/procuremesh-client.umd.js"
defer
></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
await ProcureMesh.configure({
url: "https://gateway.example.com",
checkoutButtonSelector: "button.cart__checkout-button",
checkoutButtonText: "Transfer Cart to Procurement",
onSessionStarted: () => {
document.querySelector("#punchout-banner")?.removeAttribute("hidden");
},
onCheckoutButton: (element) => {
element.classList.add("procuremesh-ready");
},
onCheckout: async () => {
const response = await fetch("/cart.js");
const data = await response.json();
return {
total_amount: data.items_subtotal_price / 100,
currency: data.currency,
items: data.items.map((line) => ({
unit_price: line.price / 100,
quantity: line.quantity,
description: line.title,
supplier_part_id: String(line.variant_id),
unit_of_measure: "EA",
classification: "14111500",
})),
};
},
onSessionEnd: async () => {
await fetch("/cart/clear.js");
},
});
});
</script>npm Usage
import { ProcureMesh } from "@charingcross/procuremesh-client";
await ProcureMesh.configure({
url: "https://gateway.example.com",
checkoutButtonSelector: "[data-procuremesh-checkout]",
onCheckout: async () => {
return {
total_amount: 125.5,
currency: "USD",
items: [
{
unit_price: 125.5,
quantity: 1,
description: "Sample item",
supplier_part_id: "SKU-1000",
unit_of_measure: "EA",
classification: "14111500",
},
],
};
},
});TypeScript
The package exports these TypeScript types:
ProcureMeshApiProcureMeshConfigTransferCartItemTransferCartPayloadTransferResponsePayload
Import example:
import {
ProcureMesh,
type ProcureMeshConfig,
type TransferCartPayload,
} from "@charingcross/procuremesh-client";If you do not need the clicked element or event in onCheckout, you can omit those parameters in your implementation.
Example:
type StorefrontCartLine = {
price: number;
quantity: number;
title: string;
variant_id: string | number;
};
type StorefrontCart = {
total_price: number;
currency: string;
items: StorefrontCartLine[];
};
import {
ProcureMesh,
type ProcureMeshConfig,
type TransferCartPayload,
} from "@charingcross/procuremesh-client";
const buildCartPayload = async (): Promise<TransferCartPayload> => {
const response = await fetch("/cart.js");
const data = (await response.json()) as StorefrontCart;
return {
total_amount: data.items_subtotal_price / 100,
currency: data.currency,
items: data.items.map((line) => ({
unit_price: line.price / 100,
quantity: line.quantity,
description: line.title,
supplier_part_id: String(line.variant_id),
unit_of_measure: "EA",
classification: "14111500",
})),
};
};
const config: ProcureMeshConfig = {
url: "https://gateway.example.com",
checkoutButtonSelector: "button.cart__checkout-button",
onCheckout: buildCartPayload,
};
await ProcureMesh.configure(config);Configuration
url
- Type:
string - Required: yes
- Set this to your ProcureMesh gateway URL
Example:
url: "https://gateway.example.com";checkoutButtonSelector
- Type:
string - Required: yes
- Set this to the CSS selector for the checkout button users click during punchout
Example:
checkoutButtonSelector: "button.cart__checkout-button";checkoutButtonText
- Type:
string - Required: no
- Replaces the checkout button label during punchout
Example:
checkoutButtonText: "Transfer Cart to Procurement";onSessionStarted
- Type:
() => void | Promise<void> - Required: no
- Use this to update the storefront UI when punchout starts
Example:
onSessionStarted: () => {
document.querySelector("#punchout-banner")?.removeAttribute("hidden");
};onSessionEnd
- Type:
() => void | Promise<void> - Required: no
- Use this to clean up the storefront before the cart is returned to procurement
Example:
onSessionEnd: async () => {
await fetch("/cart/clear.js");
};onCheckoutButton
- Type:
(element: HTMLElement) => void | Promise<void> - Required: no
- Use this to style or annotate the checkout button during punchout
Example:
onCheckoutButton: (element) => {
element.classList.add("procuremesh-ready");
element.setAttribute("aria-label", "Transfer cart to procurement");
};onCheckout
- Type:
(element: Element, event: MouseEvent) => TransferCartPayload | Promise<TransferCartPayload> - Required: yes
- Return the current storefront cart in ProcureMesh format
Example:
onCheckout: async () => {
const response = await fetch("/cart.js");
const data = await response.json();
return {
total_amount: data.items_subtotal_price / 100,
currency: data.currency,
shipping_amount: 0,
tax_amount: 0,
items: data.items.map((line) => ({
unit_price: line.price / 100,
quantity: line.quantity,
description: line.title,
supplier_part_id: String(line.variant_id),
unit_of_measure: "EA",
classification: "14111500",
image_url: line.image,
product_url: line.url,
})),
};
};Transfer Payload
Your onCheckout callback must return an object with this full shape:
shipping_amountandtax_amountare optional.short_name,manufacturer_name,manufacturer_part_id,image_url,product_url, andlead_timeare optional.classificationis required.classification_domainis optional and defaults toUNSPSC.classification_valueis still accepted for backward compatibility, butclassificationis preferred.
{
total_amount: 125.5,
currency: 'USD',
shipping_amount: 10,
tax_amount: 8.75,
items: [
{
unit_price: 50,
quantity: 2,
description: 'Printer paper A4',
supplier_part_id: 'PAPER-A4-500',
unit_of_measure: 'EA',
classification: '14111507',
short_name: 'A4 paper',
manufacturer_name: 'Acme Office',
manufacturer_part_id: 'AC-4455',
image_url: 'https://store.example.com/images/paper-a4.jpg',
product_url: 'https://store.example.com/products/paper-a4',
lead_time: '3 days'
}
]
}Template:
return {
total_amount: 0,
currency: "USD",
shipping_amount: 0,
tax_amount: 0,
items: [
{
unit_price: 0,
quantity: 1,
description: "",
supplier_part_id: "",
unit_of_measure: "EA",
classification: "",
classification_domain: "",
short_name: "",
manufacturer_name: "",
manufacturer_part_id: "",
lead_time: "",
},
],
};Top-level fields
total_amount
- Type:
number - Required: yes
- Cart subtotal excluding tax and shipping
currency
- Type:
string - Required: yes
- Three-letter ISO 4217 currency code such as
USD,GBP, orEUR
shipping_amount
- Type:
number - Required: no
- Sum of all line item shipping or cart level shipping
tax_amount
- Type:
number - Required: no
- Sum of all line item tax or cart level tax
items
- Type:
array - Required: yes
- List of cart items
Item fields
unit_price
- Type:
number - Required: yes
- Unit price for the item
quantity
- Type:
number - Required: yes
- Quantity for the item
description
- Type:
string - Required: yes
- Full item name and description shown to the buyer
supplier_part_id
- Type:
string - Required: yes
- Your storefront SKU or variant identifier
unit_of_measure
- Type:
string - Required: yes
- Unit such as
EA,BX, orPK
short_name
- Type:
string - Required: no
- Short display name for the item
classification
- Type:
string - Required: yes
- Classification code value such as
14111507 - ProcureMesh uses
UNSPSCas the default classification domain
classification_domain
- Type:
string - Required: no
- Classification system name such as
UNSPSC - Defaults to
UNSPSCwhen omitted
classification_value
- Type:
string - Required: no
- Backward-compatible alias for
classification - Prefer
classificationin new integrations
manufacturer_name
- Type:
string - Required: no
- Manufacturer name
manufacturer_part_id
- Type:
string - Required: no
- Manufacturer part number
image_url
- Type:
string - Required: no
- Public image URL for the item
product_url
- Type:
string - Required: no
- Public product page URL for the item
lead_time
- Type:
string - Required: no
- Delivery or availability text such as
3 days
Shopify Example
<script
src="https://cdn.jsdelivr.net/npm/@charingcross/[email protected]/dist/procuremesh-client.umd.js"
defer
></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
await ProcureMesh.configure({
url: "https://gateway.example.com",
checkoutButtonSelector: "button.cart__checkout-button",
checkoutButtonText: "Transfer Cart to Procurement",
onSessionStarted: () => {
document.querySelectorAll(".shopify-payment-button").forEach((el) => {
el.hidden = true;
});
},
onCheckout: async () => {
const response = await fetch("/cart.js");
const data = await response.json();
return {
total_amount: data.items_subtotal_price / 100,
currency: data.currency,
items: data.items.map((line) => ({
unit_price: line.price / 100,
quantity: line.quantity,
description: line.title,
supplier_part_id: String(line.variant_id),
unit_of_measure: "EA",
classification: "14111500",
})),
};
},
onSessionEnd: async () => {
await fetch("/cart/clear.js");
},
});
});
</script>WooCommerce Example
<script
src="https://cdn.jsdelivr.net/npm/@charingcross/[email protected]/dist/procuremesh-client.umd.js"
defer
></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
await ProcureMesh.configure({
url: "https://gateway.example.com",
checkoutButtonSelector: "a.wc-block-cart__submit-button",
checkoutButtonText: "Transfer Cart to Procurement",
onCheckout: async () => {
const response = await fetch("/wp-json/wc/store/v1/cart");
const data = await response.json();
return {
total_amount: Number(data.totals.total_items) / 100,
currency: data.totals.currency_code,
items: data.items.map((line) => ({
unit_price: line.prices.price / 100,
quantity: line.quantity,
description: line.name,
supplier_part_id: String(line.sku),
unit_of_measure: "EA",
classification: "14111500",
image_url: line.images?.[0]?.src || "",
})),
};
},
onSessionEnd: async () => {
const cartResponse = await fetch("/wp-json/wc/store/v1/cart");
const nonce = cartResponse.headers.get("nonce");
await fetch("/wp-json/wc/store/v1/cart/items", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Nonce: nonce,
},
});
},
});
});
</script>