Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
post
page
sponsor
Filter by Categories
Alternative Giving
Announcements
Business
Business Coaching
Charities We've Helped
Charity
Coding
Engineering
Guest Blog
How To
Marketing
Misc
PIFster Blog
Product Strategy
Search Engine Optimization (SEO)

Build a WordPress Pay-What-You-Want Checkout with FluentCart

A hand places a glowing Bitcoin coin into a small shopping cart on a digital surface with binary code, highlighting cryptocurrency, online shopping, and the flexibility of Pay-What-You-Want Checkout.
TL;DR: FluentCart 1.3.9 introduced Ghost Products — virtual items that bypass the product catalog entirely. We used them to build a clean donation Pay-What-You-Want checkout for PIFster, our community-driven charitable giving platform. This post walks through the pattern so you can adapt it for donations, tips, custom pricing, or any scenario where the buyer sets the amount.

The Problem

FluentCart is built for fixed-price products. That's great for most stores, but some of us need the customer to choose their own price:

  • Donation platforms (like ours)
  • Tip jars for content creators
  • Charity fundraisers
  • “Name your price” digital products

Before Ghost Products, the workaround was ugly: create a dummy product in the catalog, set a placeholder price, then override it at checkout with hooks and hidden form fields. It worked, but it meant maintaining invisible products, syncing variation IDs between environments, and bridging request data through multiple FluentCart hooks. Every new donation type meant another variation, another constant, another thing that could drift.

What Ghost Products Change

Ghost Products (docs) let you define items entirely in code. No catalog entry. No variation. No admin UI to keep in sync. You hook into fluent_cart/cart/validate_custom_item, and FluentCart hands you the item — you set the price, title, and metadata, and it processes the order normally.

The entire server-side implementation is one PHP class.

The Architecture

Here's the flow:

User picks amount → Frontend builds checkout URL
→ FluentCart fires validation hook
→ Your code sets the price → FluentCart charges via Stripe
Done

Three pieces to build:

  1. PHP handler — validates the amount and constructs the line item
  2. Frontend JS — builds the checkout URL with the user's chosen amount
  3. Hook registration — wires it up in WordPress

Step 1: The Ghost Product Handler (PHP)

This is the core. When FluentCart sees is_custom=true in the checkout request, it fires the fluent_cart/cart/validate_custom_item filter. Your handler receives the item, sets the price from the request params, and returns it.

Here's a simplified, generic version:

<?php declare(strict_types: 1); class PwywGhostProductHandler { // Arbitrary IDs — match your frontend public const ITEM_ONE_TIME = 9001; public const ITEM_MONTHLY = 9002; public const ITEM_ANNUAL = 9003; private const VALID_IDS = [ self::ITEM_ONE_TIME, self::ITEM_MONTHLY, self::ITEM_ANNUAL, ]; public function register(): void { add_filter( 'fluent_cart/cart/validate_custom_item', [$this, 'validateItem'], 10, 2 ); add_filter( 'fluent_cart/payment/validate_custom_item', [$this, 'validatePayment'], 10, 2 ); } public function validateItem(mixed $variation, mixed $item): object { $itemArray = is_array($item) ? $item : []; $itemId = (int) ($itemArray['item_id'] ?? 0); if (!in_array($itemId, self::VALID_IDS, true)) { return is_object($variation) ? $variation : (object) []; } // Read the customer's chosen amount (dollars) $amount = isset($_REQUEST['pwyw_amount']) ? (float) $_REQUEST['pwyw_amount'] : 0.0; if ($amount < 1.0) { return is_object($variation) ? $variation : (object) []; } $cents = (int) round($amount * 100); $isSubscription = $itemId !== self::ITEM_ONE_TIME; return (object) [ 'item_id' => $itemId, 'quantity' => 1, 'price' => $cents, 'unit_price' => $cents, 'line_total' => $cents, 'title' => $this->getTitle($itemId), 'is_custom' => true, 'payment_type' => $isSubscription ? 'subscription' : 'onetime', 'fulfillment_type' => 'digital', 'sold_individually' => 1, 'other_info' => [ 'payment_type' => $isSubscription ? 'subscription' : 'onetime', 'repeat_interval' => $itemId === self::ITEM_ANNUAL ? 'yearly' : 'monthly', 'pwyw_amount' => $amount, ], ]; } private function getTitle(int $itemId): string { return match ($itemId) { self::ITEM_ONE_TIME => 'One-Time Payment', self::ITEM_MONTHLY => 'Monthly Payment', self::ITEM_ANNUAL => 'Annual Payment', default => 'Payment', }; } }

Key points:

  • Prices are in cents. FluentCart works in cents internally — $25.002500.
  • is_custom => true tells FluentCart this is a ghost item.
  • payment_type controls whether FluentCart creates a one-time charge or a subscription.
  • other_info is a flexible metadata bag — FluentCart stores it as JSON on the order item.
  • repeat_interval in other_info controls subscription frequency (monthly or yearly).

Step 2: The Frontend (JavaScript)

FluentCart exposes an instant_checkout URL that skips the cart page and goes straight to payment. Your frontend just needs to build this URL with the right parameters:

function buildCheckoutUrl(siteUrl, amount, itemId) { const params = new URLSearchParams({ item_id: itemId, quantity: 1, is_custom: 'true', pwyw_amount: amount }); return siteUrl + '?fluent-cart-action=instant_checkout&' + params.toString(); } // Example: $25/month subscription const url = buildCheckoutUrl( 'https://yoursite.com', '25.00', 9002 // matches ITEM_MONTHLY constant ); window.location.href = url;

That's it. FluentCart intercepts the URL, fires your validation hook, and renders the Stripe checkout with the custom amount. Your amount input can be anything — a text field, a slider, preset buttons.

Step 3: Register the Hook

In your plugin's bootstrap (or functions.php if you're keeping it simple):

add_action('init', function () { $handler = new PwywGhostProductHandler(); $handler->register(); });

What About Subscription Renewals?

This was our biggest concern. If the item doesn't exist in the catalog, will renewals break?

They don't. FluentCart stores the full order item (including price) when the subscription is created. Stripe handles recurring billing from the stored amount. The ghost product hooks only fire on initial checkout — renewals replay the stored data. We've verified this in production with monthly and annual subscriptions.

What We Deleted

After migrating to Ghost Products, we removed ~1,400 lines of code and 4 entire PHP classes that existed solely to bridge custom amounts into FluentCart's catalog-based checkout. No more environment-specific product IDs, no more variation constants, no more cart-fill services.

Tips If You Build This

  1. Set a floor. Always validate a minimum amount server-side — don't rely on frontend validation alone.
  2. Use other_info for metadata. Need to track a donor's name, a campaign ID, or any custom context? Put it in other_info. It persists on the order item and is available in all post-purchase hooks.
  3. Start with one-time payments. Get that working first, then add subscription support. The subscription plumbing is straightforward once the basics are solid.
  4. Log everything during development. Ghost products are invisible by design — there's no admin UI to inspect. Good logging in your validation handler saves hours of debugging.

Is Pay-What-You-Want Checkout on FluentCart's Roadmap?

As of February 2026, it's not. But honestly, with Ghost Products available, a dedicated pay-what-you-want checkout feature isn't necessary. The hooks give you full control over pricing, and you can build exactly the UI your use case needs — whether that's a donation form, a tip jar, or a “name your price” product page.


PIFster is a community-driven charitable giving platform where donors vote on which charity receives the community's pooled donations each month. We run on WordPress with magic link authentication, gift memberships for donor acquisition, and a gamified referral engine. If you're building something similar, we'd love to hear about it.

About The Author

PIFster Admin

Founder and administrator of PIFster, the Pay It Forward charity. We thank you for being here.
Leave a Reply

Your email address will not be published. Required fields are marked *

    Illustration of three people sitting at a table with colorful speech bubbles above them on a light blue background. Large text in the center reads “Join Newsletter.” Perfect for charity, giving, or help-themed campaigns.
    Footer Form

    © 2026 PIFster. All rights reserved. | PIFster is Veteran Owned & Operated | EIN: 92-1821142