Hulo Global
Plugins Roadmap

Checkout Guard

The safety layer around Vendure checkout and payments: Stripe manual-capture holds that actually place the order, bank transfer with auto-expiry, failed-payment and orphaned-charge visibility with nightly Stripe reconciliation, session-bound order lookup, checkout rate limits and a funnel — in one admin dashboard.

315 npm downloads last monthSubmitted to the Vendure plugin directoryVendure 3.5 – 3.7MySQL · MariaDB · PostgreSQLAGPL source + commercial licence

What it does

Built for production from day one.

Vendure's Stripe integration settles a payment when the intent succeeds — and nothing else. Put a manual-capture hold on a large order and the customer sees a confirmation page while the order never places and the authorisation silently expires. Enable bank transfer and there is no expiry, no reminder, no reconciliation. A card declines and no one ever knows. Checkout Guard closes those gaps server-side: an Authorized payment for every hold with capture, cancel and a safety capture before Stripe's seven-day window closes; a bank-transfer method that carries the account details to the storefront, expires unpaid orders and tells you when money is due; a payment-event log that records webhook failures, storefront declines, orphaned charges and amount drift, with a nightly Stripe reconciliation; an order-lookup strategy bound to the session that placed the order; a trusted client-IP contract for your server-side proxy; rate limits on the checkout mutations that get brute-forced; and funnel events so you can finally see where a checkout is lost.

Manual-capture holds that place the order

A `stripe-hold` payment handler plus a signed webhook for `payment_intent.amount_capturable_updated`: the order becomes PaymentAuthorized the moment the hold is placed. Capture or cancel from the dashboard or the Vendure order page; a safety capture runs before the authorisation expires; alerts fan out to your ops channels.

Bank transfer done properly

Account details travel in the payment's public metadata so the storefront and confirmation page render them without hard-coding; an eligibility checker (min/max, guests, customer groups); unpaid orders expire automatically after N days with a reminder event before that; a "received" button settles the order and releases fulfilment.

Failed payments you can see

Webhook declines, storefront-reported declines, orphaned charges and settled amounts that differ from the order total are written to a payment-event log with the IP — so fraud tooling can count them and you can spot a broken checkout before customers email you.

Nightly Stripe reconciliation

Every succeeded or held PaymentIntent from the last days is matched to a Vendure payment; anything unmatched is flagged and alerted, per channel, with the Stripe key each channel already has.

Session-bound order lookup

Vendure lets anyone with an order code read a fresh order for two hours. Checkout Guard binds anonymous access to the session that placed it — the confirmation page still works, strangers with a code do not.

Trusted client IP

A shared-secret header contract for server-side proxies (Qwik server$, Next.js route handlers) so the order IP that feeds fraud scoring cannot be spoofed by a browser.

Checkout rate limits

Token-bucket limits on applyCouponCode, addPaymentToOrder, createStripePaymentIntent and transitionOrderToState, keyed by client IP, returning a clean 429 — stops coupon brute force and intent spam without touching nginx.

Checkout funnel

Step, attempt, failure and coupon-rejection events from the storefront, summarised per step with drop-off percentages, so single-URL checkouts stop being invisible to analytics.

Ops alerts

Slack, Discord, Microsoft Teams, Telegram, a signed webhook and email — every hold, expiry, orphan and drift can ping the channel your team actually reads.

Admin dashboard

Overview KPIs, Holds, Bank transfers, Payment events, Funnel and Settings tabs, plus the Licence & billing card. Light and dark themes.

MySQL, MariaDB & PostgreSQL

The plugin follows whatever database your Vendure `dbConnectionOptions` use — no configuration. Verified against PostgreSQL 17; MySQL/MariaDB installs are unchanged.

Buy & activate from the admin

Start the 14-day free trial, subscribe or buy lifetime from the plugin's admin page — checkout opens in a new tab and the key installs itself within a minute, renewals included. The same card shows your plan, first-charge date and a <strong>Manage billing</strong> link to the Stripe portal (update card, cancel, switch plan). Already have a key? Paste it into the same banner. No `.env` edit, no redeploy; environment keys still take precedence for infrastructure-as-code setups.

One-click in-app updates

When a new version ships, an update banner shows current → latest with a What's-new link to the changelog. "Update now" installs the registry-verified release via your project's own package manager (yarn/npm/pnpm auto-detected) and gracefully restarts under pm2/systemd. Disable with `HULO_SELF_UPDATE=off`.

Install

Three steps, five minutes.

Add the package

Or run the one-line installer that does steps 1–3 for you:

curl -sSL https://huloglobal.com/vendure-plugins/checkout-guard/install.sh | bash

Prefer to do it by hand?

yarn add @huloglobal/vendure-plugin-checkout-guard # or npm install @huloglobal/vendure-plugin-checkout-guard # or pnpm add @huloglobal/vendure-plugin-checkout-guard

Register it

In your vendure-config.ts:

# vendure-config.ts import { CheckoutGuardPlugin } from '@huloglobal/vendure-plugin-checkout-guard'; export const config: VendureConfig = { plugins: [ CheckoutGuardPlugin.init({ publicBaseUrl: 'https://shop.example.com', licenceKey: process.env.HULO_LICENCE_KEY_CHECKOUT_GUARD, }), // ... your other plugins ], };

Compile the admin UI

Add the extension to your compileUiExtensions call so the plugin's page appears in the admin:

// compile-admin-ui.ts import { compileUiExtensions } from '@vendure/ui-devkit/compiler'; import { CheckoutGuardPlugin } from '@huloglobal/vendure-plugin-checkout-guard'; compileUiExtensions({ outputPath: path.join(__dirname, 'admin-ui'), extensions: [CheckoutGuardPlugin.uiExtensions], });
No migration to run. The plugin creates its tables on first boot and upgrades them in place on later versions.

That's it. Restart Vendure and the plugin's page is in the admin. Without a key it runs in the free tier; open the page and click Start 14-day free trial to switch everything on — the key installs itself, no .env edit, no redeploy.

Free tier vs licensed

Try the whole thing, keep the core for free.

Install without a key and everything on the left works indefinitely. The 14-day trial switches the right-hand column on with your real traffic; a licence keeps it on.

Free, no key

  • Session-bound order lookup and trusted client IP
  • Rate limits and funnel events
  • Bank-transfer handler and eligibility checker
  • The dashboard

Trial + licensed

  • Stripe manual-capture hold handling
  • Bank-transfer auto-expiry and reminders
  • Failed-payment recording and nightly reconciliation
  • Amount-drift guard and ops alerts
Start 14-day free trial →

HTTP endpoints

Every route exposed.

POST/checkout-guard/stripe-webhookStripe: signed webhook for holds + failures
GET/checkout-guard/holdsAdmin: Authorized Stripe holds
POST/checkout-guard/holds/:id/captureAdmin: capture a hold (settles the payment)
POST/checkout-guard/holds/:id/cancelAdmin: release a hold
GET/checkout-guard/bank-transfersAdmin: bank transfers awaiting / expired / settled
POST/checkout-guard/bank-transfers/:id/receivedAdmin: mark a transfer received
POST/checkout-guard/client-declinePublic: storefront-reported card decline (rate limited)
POST/checkout-guard/funnelPublic: checkout funnel event (rate limited)
GET/checkout-guard/eventsAdmin: payment-event log
GET/checkout-guard/summaryAdmin: dashboard KPIs
GET/checkout-guard/funnel/summaryAdmin: per-step funnel with drop-off
GET/checkout-guard/licence/statusAdmin: licence + evaluation + update status
POST/checkout-guard/licence/activateAdmin: activate a licence key from the admin UI
POST/checkout-guard/update/runAdmin: one-click in-app update + graceful restart

FAQ

Common questions.

How do I get a licence key?

Buy here — Stripe Checkout — monthly, annual (two months free) or lifetime. You'll receive the JWT key by email. Paste it into the plugin's admin settings (Activate) — no redeploy — or set it as HULO_LICENCE_KEY_CHECKOUT_GUARD in your .env if you prefer config-as-code; the env key wins when both are present.

Does it work without a key?

Yes — every subscription starts with a 14-day free trial. Install the plugin, open its admin page and click Start 14-day free trial: a card is required, nothing is charged until day 15, and you can cancel any time before then. The licence installs itself and every premium feature is on for the whole trial with your real traffic.

Which databases are supported?

MySQL, MariaDB and PostgreSQL (verified against PostgreSQL 17). The plugin follows your Vendure dbConnectionOptions automatically — there is nothing to configure.

How do updates work?

The plugin checks the npm registry daily. When a newer version exists, the admin dashboard shows an update banner with a What's-new link to the changelog and an "Update now" button that installs the registry-verified release via your own package manager and gracefully restarts under your process supervisor. Prefer manual control? Copy the install command instead, or set HULO_SELF_UPDATE=off.

Where is data stored?

In your Vendure database. The plugin adds its own tables (created on boot, or via a migration for the plugins that register entities) — your data never leaves your server.

Will it survive a Vendure upgrade?

Tested against Vendure >=3.5.0 <4.0.0 — 3.5, 3.6 and 3.7 are all covered by CI. A boot-time compatibility check emits a non-fatal warning if @vendure/core is outside that range, so upgrades to a future 3.x are safe to try. The 4.0 line will be tested and re-declared once its changelog lands.

Ready to ship?

Install in five minutes, run the trial on real traffic, keep it if it earns its place.