Vendure 3.6+ plugin for Printful print-on-demand integration.
- Idempotent fulfillment submission — exactly-once outbound calls to
Printful's
POST /ordersdriven by a BullMQ job with a deterministicjobId, backed by aPrintfulSubmissionStatetable with lease-based atomic claims. - Raw-body HMAC webhook —
/printful/webhookregistered withbeforeListen: trueso HMAC verification runs against the original request bytes. Webhook dedupe viaPrintfulWebhookDedupe. - Sync record cache — caches Printful sync products so storefront variant lookups don't round-trip to Printful on every request.
- Admin retry mutation —
printfulRetrySubmission(fulfillmentId)re-enqueues via the order service (preserving the jobId guardrail) for manual recovery. - Stub / live API client toggle — outbound calls go to a stub when
apiTokenis unset (dev default), live when set (prod).
yarn add @harder-labs/vendure-plugin-printful
# peer:
yarn add @vendure/coreimport { PrintfulPlugin } from '@harder-labs/vendure-plugin-printful';
const config: VendureConfig = {
plugins: [
PrintfulPlugin.init({
apiToken: process.env.PRINTFUL_API_TOKEN,
storeId: process.env.PRINTFUL_STORE_ID
? +process.env.PRINTFUL_STORE_ID
: undefined,
webhookSecret: process.env.PRINTFUL_WEBHOOK_SECRET,
confirmOrders: process.env.PRINTFUL_CONFIRM_ORDERS !== 'false',
}),
],
};| Entity | Field | Purpose |
|---|---|---|
ProductVariant |
printfulVariantId (number) |
links a Vendure variant to a Printful catalog variant |
ProductVariant |
printfulSyncProductId (number) |
links to the Printful sync product |
ProductVariant |
printCostCents (number) |
Printful's print cost for margin-floor checks |
ProductVariant |
mockupUrl (string) |
preview image for storefront cards |
Channel |
minimumMarginCents (number) |
floor that POD lines can't sell below — enforced by the platform's margin-floor OrderProcess |
Fulfillment |
printfulOrderId (number) |
Printful's order id (post-submit) |
Fulfillment |
printfulStatus (string) |
last-seen status from webhook |
| Var | Required for prod? | Purpose |
|---|---|---|
PRINTFUL_API_TOKEN |
yes | Printful API token; unset → stub client |
PRINTFUL_STORE_ID |
yes | Printful store id for the channel |
PRINTFUL_WEBHOOK_SECRET |
yes | HMAC verification secret for /printful/webhook |
PRINTFUL_CONFIRM_ORDERS |
optional (default true) |
whether to confirm orders on submit; false leaves them as drafts |
EventBussubscriber onOrderStateTransitionEvent(PaymentSettled → submit)OnApplicationBootstrap— BullMQ queue ownership lives inPrintfulOrderServiceso admin retries don't bypass the jobId guardrail.
Extracted from the vendure-platform
in-tree implementation (Phase 0 tasks #7 + #14). Two gpt-5.4 review rounds
hardened the lease-claim + admin-retry paths and split the webhook error
taxonomy. See CHANGELOG.md.