OmnifyWP v1.2.0
omnifywp.com wp.org GitHub ★ Star
Next-Generation Headless & Decoupled WordPress Commerce

The Developer-First
eCommerce Engine for WordPress

Omnify replaces sluggish wp_posts and wp_postmeta overhead with a purpose-built 18-table normalized SQL relational engine, atomic stock management, cryptographic link signing, and complete REST APIs.

18
Custom Relational SQL Tables
< 1.2ms
Database Index Lookup Time
10+
Integrated Payment Gateways
100%
Decoupled REST API Coverage

System Pipeline & Data Flow

Omnify processes requests cleanly through decoupled application layers:

💻
Storefront / Client
Shortcodes or Next.js Headless
➔
⚡
REST / Auth
Omnify_Rest_Server + Nonce/Keys
➔
📦
Repositories SDK
Domain Logic & Validation
➔
🗄️
Custom DB Schema
18 Indexed Tables + Object Cache
➔
🔒
Delivery & Pay
Stripe, PayPal & HMAC Locker

Quickstart & Installation

Omnify is distributed as a clean, self-contained WordPress plugin with zero external PHP dependencies.

Terminal — Cloning and Building the Package
# 1. Clone the repository
git clone https://github.com/omnifywp/omnifywp-ecommerce.git wp-content/plugins/omnify

# 2. Package into a distribution ZIP ready for installation
cd wp-content/plugins/omnify
node build.js

# 3. Output artifact: dist/omnifywp-ecommerce.zip

Runtime Requirements

ComponentMinimumRecommendedNotes
PHP 8.2.0+ 8.3+ Strict types, typed properties, match expressions, and union return types.
WordPress 5.8+ 6.4+ Compatible with both Classic PHP themes and Full Site Editing (FSE).
MySQL 5.7+ / MariaDB 10.3+ MySQL 8.0+ InnoDB engine with utf8mb4 collation support for emojis and global scripts.

Architecture & Dependency Injection

Omnify utilizes an internal dependency container (Omnify_Container) exposed by the singleton Omnify_Plugin. Every repository, gateway, and service class is auto-wired with strict constructor injection.

PHP — Accessing the Container
use Omnify\eCommerce\Repositories\Omnify_Product_Repository;
use Omnify\eCommerce\Repositories\Omnify_Order_Repository;

// Fetch the plugin container anywhere in WordPress
$container = omnify()->container();

// Resolve auto-wired repositories
$product_repo = $container->get(Omnify_Product_Repository::class);
$order_repo   = $container->get(Omnify_Order_Repository::class);

// Query products directly
$products = $product_repo->all([
    'status'   => 'published',
    'per_page' => 20,
]);

Database Engine & 18 Relational Tables

Omnify isolates all commercial data from the WordPress post engine. Tables are prefixed with wp_omnify_* and indexed for high-concurrency throughput.

The 18 Core Database Tables

TablePrimary FunctionIndices & Keys
wp_omnify_productsMaster catalog records (download, simple, variable, bundle)PRIMARY (id), UNIQUE KEY (slug), KEY (status, type)
wp_omnify_product_variationsFormat matrix (SKU, price swatches, attributes)PRIMARY (id), KEY (product_id)
wp_omnify_product_filesPhysical files, versions, and attachmentsPRIMARY (id), KEY (product_id), KEY (expires_at)
wp_omnify_product_categoriesCategory terms & taxonomy linkagesPRIMARY (id), KEY (product_id), KEY (slug)
wp_omnify_product_brandsProduct brands & manufacturer termsPRIMARY (id), KEY (product_id), KEY (slug)
wp_omnify_product_tagsFaceted search and discovery tagsPRIMARY (id), KEY (product_id), KEY (slug)
wp_omnify_ordersOrders, financial totals, currency, and tax trackingPRIMARY (id), UNIQUE KEY (order_number), KEY (customer_id, status)
wp_omnify_order_itemsSnapshot of purchased items, prices, and quantitiesPRIMARY (id), KEY (order_id), KEY (product_id)
wp_omnify_order_notesStaff internal comments & customer visibility updatesPRIMARY (id), KEY (order_id), KEY (customer_visible)
wp_omnify_customersCustomer CRM profiles, addresses, and tax exemptionsPRIMARY (id), UNIQUE KEY (email), KEY (user_id), KEY (status)
wp_omnify_customer_notesCRM support notes attached to customer accountsPRIMARY (id), KEY (customer_id), KEY (customer_visible)
wp_omnify_customer_activityAudit trail (logins, checkout started, address changes)PRIMARY (id), KEY (customer_id), KEY (activity_type)
wp_omnify_customer_accessDigital file entitlements & access permissionsPRIMARY (id), UNIQUE KEY (customer_id, product_id)
wp_omnify_downloadsCryptographic download stream transaction logsPRIMARY (id), KEY (product_id), KEY (file_id), KEY (customer_id)
wp_omnify_couponsPromotional codes, percentage/fixed rules, limitsPRIMARY (id), UNIQUE KEY (code)
wp_omnify_reviewsProduct ratings, content, and approval statesPRIMARY (id), KEY (product_id), KEY (status)
wp_omnify_abandoned_cartsCaptured checkout inputs, recovery tokens, remindersPRIMARY (id), UNIQUE KEY (token), KEY (email, status)
wp_omnify_wishlistsCustomer wishlist favorite associationsPRIMARY (id), UNIQUE KEY (user_id, product_id)

Soft Deletion & Caching Engine

Soft deletion is native across all core tables via a nullable deleted_at timestamp. Trashed items are excluded from storefront catalog queries, star rating averages, urgency counters, and public REST queries.

Repositories SDK Reference

All database reads and writes are encapsulated inside typed repository classes.

Product Repository (Omnify_Product_Repository)

PHP — Product Queries, Stock & Variations
$products = $container->get(\Omnify\eCommerce\Repositories\Omnify_Product_Repository::class);

// 1. Filter products with pagination and faceted criteria
$items = $products->all([
    'status'       => 'published',
    'category'     => 'Templates',
    'type'         => 'download',
    'price_min'    => 10.00,
    'price_max'    => 120.00,
    'stock_status' => 'instock',
    'sort'         => 'rating_desc',
    'per_page'     => 20,
]);

// 2. Atomic stock decrement (prevents checkout race conditions)
$success = $products->reduce_stock(
    product_id: 10,
    variation_id: null,
    quantity: 1
);

Order Repository (Omnify_Order_Repository)

PHP — Creating Orders & Adding Notes
$orders = $container->get(\Omnify\eCommerce\Repositories\Omnify_Order_Repository::class);

// Batch-hydrated order listing (eliminates N+1 queries)
$recent_orders = $orders->all([
    'status'   => 'completed',
    'per_page' => 50,
]);

Customer Repository (Omnify_Customer_Repository)

PHP — Customer Lookups & Audit Timeline
$customers = $container->get(\Omnify\eCommerce\Repositories\Omnify_Customer_Repository::class);

// Find customer by WordPress User ID
$customer = $customers->find_by_user_id(get_current_user_id());

// Record custom CRM event in the customer's timeline
$customers->record_activity(
    customer_id: $customer['id'],
    activity_type: 'vip_upgrade',
    metadata: ['tier' => 'Gold', 'discount' => 0.15]
);

Coupon Repository (Omnify_Coupon_Repository)

PHP — Creating & Validating Coupons Programmatically
$coupons = $container->get(\Omnify\eCommerce\Repositories\Omnify_Coupon_Repository::class);

// Create a single-use 20% discount coupon
$coupon_id = $coupons->create([
    'code'           => 'SUMMER20',
    'discount_type'  => 'percentage',  // 'percentage' or 'fixed'
    'discount_value' => 20.00,
    'usage_limit'    => 100,
    'expires_at'     => '2026-12-31 23:59:59',
]);

Storefront & Shortcodes Directory

ShortcodeDefault SlugFunctionality
[omnify_storefront]/storefront/Main catalog grid with layout style toggle, search, brand/category filters, and single product views.
[omnify_cart]/cart/Multi-item cart, scarcity countdown timer, shipping zone selector, and coupon application.
[omnify_checkout]/checkout/Frictionless single-page checkout with country-first address autofill and gateway buttons.
[omnify_customer_portal]/customer-portal/Customer dashboard displaying orders, profile address book, and wishlist favorites.
[omnify_customer_downloads]/downloads/Customer digital locker displaying owned files and cryptographically signed download links.
[omnify_order_tracking]/order-tracking/Order lookup supporting guest tracking with direct digital downloads for paid items.

Storefront.js Client-Side State Machine

Storefront operations run through assets/storefront/storefront.js. Configuration parameters are injected under window.omnifyStorefront.

Payment Gateways & Refunds

Omnify provides native API integrations for Stripe, PayPal Commerce, Razorpay, Alipay, WeChat Pay, SSLCommerz, Paystack, Tap Payments, Mollie, and manual methods.

When an admin processes a refund, the gateway API is executed, the order is updated, and all digital file access rights in wp_omnify_customer_access are revoked automatically.

Digital Locker & Chunked Streaming

Download links are protected using HMAC-SHA256 signatures salted with wp_salt('auth'). Binary delivery streams files in 8KB chunks via fread() with output buffers flushed to prevent server memory exhaustion.

Interactive REST API Sandbox & Simulator

Simulate live HTTP requests against Omnify's decoupled endpoints. Select any endpoint to inspect headers, request schemas, parameters, and interactive JSON responses with real-world latency metrics.

GET
List published catalog products with search, pagination, category, and faceted price filtering.
200 OK ⚡ 1.2ms 📦 1.4 KB
Request Headers & Body No Body (GET)
// Request Headers
Accept: application/json
Content-Type: application/json
X-WP-Nonce: e9b41a29f8
JSON Response Payload
...

REST API Reference

Namespace: /wp-json/omnify/v1

Storefront Public Endpoints

GET /wp-json/omnify/v1/products/public
Public
List published products with search, category, brand, and faceted price filtering.
POST /wp-json/omnify/v1/checkout
Public / Guest
Process checkout order, validate stock and coupon, and return transaction receipt.

Administrative Management Endpoints

MethodEndpointDescription
GET/ordersPaginated orders list with status, date, and customer filters.
GET/orders/{id}Detailed order information with line items and customer notes.
POST/orders/{id}/mark-paidTransitions order to paid, reduces stock, and grants downloads.
POST/orders/{id}/trashSoft-deletes order to trash.
POST/orders/{id}/restoreRestores order from trash.
GET/customers/{id}/activityRetrieves audit timeline events for a customer.
POST/accessManually grants digital file access to a customer.
POST/access/revokeRevokes file download entitlements.

Action Hooks Directory

Action hooks let you execute custom logic when specific events happen inside Omnify.

Action HookArgumentsFires When
omnify_before_boot$plugin, $containerFires before Omnify registers internal services and repositories.
omnify_booted$plugin, $containerFires after full bootstrap is complete.
omnify_order_created$order_id, $order_dataFires immediately after an order is recorded in the database.
omnify_order_status_changed$order_id, $new_status, $old_statusFires when an order transitions between statuses (e.g. pending → completed).
omnify_customer_access_granted$customer_id, $product_id, $expires_at, $idFires when digital product downloads are granted to a customer.
omnify_customer_access_revoked$customer_id, $product_idFires when access is revoked upon refund or cancellation.
omnify_download_logged$log_id, $product_id, $file_id, $customer_id, $statusFires after a customer downloads a protected file.
omnify_stock_reduced$product_id, $variation_id, $qty, $new_stockFires after atomic stock reduction on checkout.
PHP — Example: Triggering Fulfillment when Order Status Changes
add_action('omnify_order_status_changed', function($order_id, $new_status, $old_status) {
    if ('processing' === $new_status || 'completed' === $new_status) {
        // Fetch order details from repository
        $order = omnify()->container()->get(\Omnify\eCommerce\Repositories\Omnify_Order_Repository::class)->find($order_id);
        
        // Log custom message or dispatch external fulfillment
        error_log("Order #{$order_id} is ready for fulfillment for {$order['email']}");
    }
}, 10, 3);

Filter Hooks Directory

Filter hooks allow developers to intercept, modify, and return customized data throughout the Omnify engine without modifying a single core file. Whenever Omnify formats prices, processes checkout submissions, queries products, or streams digital files, it passes the data through these hooks.

Filter HookParametersReturn TypePrimary Use Case
omnify_format_price $formatted, $amount, $settings string Customize currency display, append VAT notes, display "FREE" badges, or perform dynamic multi-currency conversions.
omnify_before_checkout_validation $data, $settings, $items array Normalize or sanitize form inputs (e.g. phone numbers), apply cart-wide volume tier discounts, or inject session variables.
omnify_after_checkout_validation $error, $data, $settings, $items null|WP_Error Halt checkout by returning a WP_Error. Enforce minimum order values, block disposable emails, or validate VAT/tax IDs.
omnify_get_products_args $args array Modify product query arguments. Automatically hide out-of-stock items or gate VIP/wholesale products by user role.
omnify_get_orders_args $args array Filter order listings in admin or customer portals (e.g. by customer tier, status, or date range).
omnify_signed_download_expires_in $seconds, $file_id, $customer_id int Adjust the lifetime (default: 900s / 15m) of HMAC-SHA256 download links. Offer 48-hour windows for VIP members.
omnify_rest_can_manage $can_manage, $request, $user_id bool|WP_Error Authorize external ERP microservices, inventory sync bots, or headless services via custom API keys or Bearer tokens.
omnify_email_arguments $args array Intercept transactional emails. Attach PDF invoices, customize HTML templates, or add accounting team BCC addresses.

Detailed Filter Hook Guides & Practical Examples

Filter Hook #1 Returns: string

omnify_format_price($formatted, $amount, $settings)

How it works: Every price string rendered by Omnify across storefront cards, quick-view modals, cart drawers, checkout summaries, and email confirmations passes through this filter. You can rewrite the text, append tax notices, or convert the currency symbol.

Parameters: $formatted (string) — Formatted price HTML/text (e.g., "$49.00") • $amount (float) — Raw numerical price (e.g., 49.00) • $settings (array) — Active store currency and formatting options.
Example A — Display "FREE" for Zero-Dollar Products
/**
 * Display a stylish "FREE" badge instead of "$0.00" for giveaway digital goods.
 */
add_filter('omnify_format_price', function($formatted, $amount, $settings) {
    if ((float) $amount === 0.0) {
        return '<span class="badge-free" style="color:#10b981; font-weight:700;">FREE</span>';
    }
    return $formatted;
}, 10, 3);
Example B — Append Currency Code & VAT Notice
/**
 * Append international ISO code (e.g. USD) and VAT tax indicator.
 */
add_filter('omnify_format_price', function($formatted, $amount, $settings) {
    $currency = $settings['currency'] ?? 'USD';
    return $formatted . " <span style=\"font-size:0.8em; color:#94a3b8;\">{$currency} (incl. VAT)</span>";
}, 10, 3);
Filter Hook #2 Returns: array

omnify_before_checkout_validation($data, $settings, $items)

How it works: Executes before Omnify performs required-field verification. Use this to sanitize and clean user inputs, normalize phone numbers to international standard, or modify cart data.

Parameters: $data (array) — Form submission keys (first_name, email, address, custom fields) • $settings (array) — Store options • $items (array) — Line items in cart.
PHP — Normalize Phone Numbers & Sanitize Custom Fields
/**
 * Standardize customer phone numbers to digits only and strip spaces.
 */
add_filter('omnify_before_checkout_validation', function($data, $settings, $items) {
    if (! empty($data['phone'])) {
        // Strip all non-numeric characters except leading plus '+'
        $data['phone'] = preg_replace('/[^\d+]/', '', $data['phone']);
    }

    // Sanitize customer delivery notes if provided
    if (! empty($data['delivery_notes'])) {
        $data['delivery_notes'] = sanitize_textarea_field($data['delivery_notes']);
    }

    return $data;
}, 10, 3);
Filter Hook #3 Returns: null | WP_Error

omnify_after_checkout_validation($error, $data, $settings, $items)

How it works: The most crucial hook for custom business rules. Fires after all core validations pass. If you return null, the checkout succeeds. If you return a WP_Error object, checkout immediately stops and your message is highlighted in red on the checkout screen.

Parameters: $error (null|\WP_Error) — Initial state • $data (array) — Validated checkout data • $settings (array) — Store settings • $items (array) — Line items.
Example A — Enforce Minimum Order Subtotal ($25.00)
/**
 * Block checkout if the customer's total purchase is less than $25.00.
 */
add_filter('omnify_after_checkout_validation', function($error, $data, $settings, $items) {
    $subtotal = 0.0;
    foreach ($items as $item) {
        $subtotal += (float) ($item['price'] ?? 0) * (int) ($item['quantity'] ?? 1);
    }

    if ($subtotal < 25.00) {
        return new \WP_Error(
            'subtotal_too_low',
            __('Our store requires a minimum purchase subtotal of $25.00.', 'my-store')
        );
    }

    return $error;
}, 10, 4);
Example B — Block Fraudulent Throwaway / Disposable Emails
/**
 * Reject temporary disposable emails (e.g. mailinator, tempmail) to protect digital downloads.
 */
add_filter('omnify_after_checkout_validation', function($error, $data) {
    $email = strtolower(trim($data['email'] ?? ''));
    $blocked_domains = ['mailinator.com', 'tempmail.com', '10minutemail.com', 'guerrillamail.com'];

    foreach ($blocked_domains as $domain) {
        if (str_ends_with($email, '@' . $domain)) {
            return new \WP_Error(
                'blocked_email_provider',
                __('Temporary or disposable email addresses are not permitted. Please use your real email.', 'my-store')
            );
        }
    }

    return $error;
}, 10, 2);
Filter Hook #4 Returns: array

omnify_get_products_args($args)

How it works: Intercepts parameters sent to Omnify_Product_Repository::all(). Use this to automatically filter storefront query results without modifying templates.

PHP — Automatically Hide Out-of-Stock Items & Restrict VIP Catalog
/**
 * Enforce in-stock only for visitors, and restrict category 'wholesale' to B2B users.
 */
add_filter('omnify_get_products_args', function($args) {
    // 1. Always hide out of stock items on public frontend
    if (! is_admin()) {
        $args['stock_status'] = 'instock';
    }

    // 2. If user is NOT a verified wholesale buyer, exclude wholesale catalog
    if (! current_user_can('wholesale_buyer')) {
        $args['exclude_category'] = 'wholesale-exclusive';
    }

    return $args;
});
Filter Hook #5 Returns: int (Seconds)

omnify_signed_download_expires_in($seconds, $file_id, $customer_id)

How it works: Omnify signs digital downloads using HMAC-SHA256 to stop link piracy. By default, links expire after 900 seconds (15 minutes). Use this hook to lengthen or shorten the window depending on file size or customer status.

PHP — Extend Download Window to 48 Hours for VIP Members
/**
 * VIP customers get 48 hours (172,800s); regular customers get 1 hour (3,600s).
 */
add_filter('omnify_signed_download_expires_in', function($seconds, $file_id, $customer_id) {
    $customer = omnify()->container()->get(\Omnify\eCommerce\Repositories\Omnify_Customer_Repository::class)->find($customer_id);

    if ($customer && ($customer['group'] ?? '') === 'vip') {
        return 172800; // 48 hours in seconds
    }

    return 3600; // 1 hour for standard customers
}, 10, 3);
Filter Hook #6 Returns: bool | WP_Error

omnify_rest_can_manage($can_manage, $request, $server)

How it works: Controls administrative access to REST API routes under /wp-json/omnify/v1/. Return true to grant authorization, or return a WP_Error to deny access. Perfect for headless systems and automated ERP bots.

PHP — Authorize External ERP Inventory Sync via Secret API Token
/**
 * Allow automated inventory sync bot using a custom Bearer token.
 */
add_filter('omnify_rest_can_manage', function($can_manage, $request) {
    $auth_header = $request->get_header('authorization') ?? '';
    $secret_token = 'erp_live_sec_994827104829104820';

    if (hash_equals('Bearer ' . $secret_token, $auth_header)) {
        return true; // Authorized external sync
    }

    return $can_manage;
}, 10, 2);
Filter Hook #7 Returns: array

omnify_email_arguments($args)

How it works: Intercepts transactional emails before dispatch. You can add BCC headers for internal order archiving or attach PDF receipts and license agreements.

PHP — Add Accounting Team BCC & Attach PDF Terms
/**
 * Automatically BCC accounting team on all order confirmation emails.
 */
add_filter('omnify_email_arguments', function($args) {
    if (str_contains($args['subject'] ?? '', 'Order Confirmation')) {
        $args['headers'][] = 'Bcc: accounting@my-company.com';
    }
    return $args;
});

Developer Recipes & Real-World Guides

Complete, production-tested, copy-pasteable step-by-step guides for extending Omnify in real-world commercial WordPress deployments.

Recipe 1 • Checkout Customization

Custom Checkout Fields (VAT / Tax ID & Delivery Instructions)

Problem: You need B2B customers to enter a European VAT ID or shipping instructions on checkout, validate it, and store it visibly in the order history.

PHP — Step 1: Validation with omnify_after_checkout_validation
/**
 * Step 1: Validate custom VAT number during checkout.
 */
add_filter('omnify_after_checkout_validation', function($error, $data) {
    $vat = trim($data['vat_number'] ?? '');
    
    // If customer provided a VAT ID, ensure it has at least 8 alphanumeric characters
    if (! empty($vat) && strlen($vat) < 8) {
        return new \WP_Error('invalid_vat', __('Please provide a valid VAT / Tax registration ID.', 'my-theme'));
    }
    
    return $error;
}, 10, 2);
PHP — Step 2: Store in Order Notes via Omnify_Order_Repository
/**
 * Step 2: Save custom VAT and delivery instructions into Order Notes upon creation.
 */
add_action('omnify_order_created', function($order_id, $order_data) {
    $orders = omnify()->container()->get(\Omnify\eCommerce\Repositories\Omnify_Order_Repository::class);
    
    if (! empty($_POST['vat_number'])) {
        $vat = sanitize_text_field(wp_unslash($_POST['vat_number']));
        $orders->add_note($order_id, "Customer VAT / Tax ID: {$vat}", customer_visible: true);
    }

    if (! empty($_POST['delivery_notes'])) {
        $notes = sanitize_textarea_field(wp_unslash($_POST['delivery_notes']));
        $orders->add_note($order_id, "Delivery Instructions: {$notes}", customer_visible: true);
    }
}, 10, 2);
Recipe 2 • Automation & Notifications

Real-Time Discord or Slack Order Alerts

Problem: Your operations team needs an instant rich notification in Discord or Slack whenever a customer completes a purchase.

PHP — Webhook Dispatch on Order Completion
/**
 * Send real-time webhook alert to Discord or Slack when an order is paid.
 */
add_action('omnify_order_status_changed', function($order_id, $new_status) {
    // Trigger only when order reaches 'completed' or 'processing'
    if (! in_array($new_status, ['completed', 'processing'], true)) {
        return;
    }

    $order = omnify()->container()->get(\Omnify\eCommerce\Repositories\Omnify_Order_Repository::class)->find($order_id);
    if (! $order) {
        return;
    }

    $webhook_url = 'https://discord.com/api/webhooks/YOUR_WEBHOOK_URL_HERE';

    $payload = [
        'embeds' => [
            [
                'title'       => "🎉 New Order #{$order_id} Received!",
                'color'       => 3921912, // Emerald green
                'description' => "**Customer:** {$order['billing_first_name']} {$order['billing_last_name']} ({$order['email']})\n"
                               . "**Total:** {$order['currency']} \${$order['total']}\n"
                               . "**Gateway:** {$order['payment_method']}\n"
                               . "**Status:** {$new_status}",
                'timestamp'   => date('c')
            ]
        ]
    ];

    wp_remote_post($webhook_url, [
        'headers'  => ['Content-Type' => 'application/json'],
        'body'     => wp_json_encode($payload),
        'blocking' => false, // Non-blocking async dispatch for maximum performance
        'timeout'  => 5,
    ]);
}, 10, 2);
Recipe 3 • Headless Frontend

Headless Next.js 14 / React Catalog & Headless Checkout

Problem: You want a lightning-fast React frontend querying Omnify's public endpoints using Next.js Incremental Static Regeneration (ISR) and server components.

TypeScript / Next.js 14 — Catalog Fetching with 60s Revalidation
// app/products/page.tsx (Next.js 14 App Router Server Component)
interface Product {
  id: number;
  name: string;
  slug: string;
  price: number;
  sale_price: number | null;
  stock_status: string;
}

export default async function ProductsPage() {
  const res = await fetch('https://your-site.com/wp-json/omnify/v1/products/public?per_page=12', {
    next: { revalidate: 60 } // Revalidate every 60 seconds (ISR)
  });
  const products: Product[] = await res.json();

  return (
    <div className="max-w-6xl mx-auto py-12 px-4">
      <h1 className="text-3xl font-extrabold mb-8">Product Catalog</h1>
      <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
        {products.map((p) => (
          <div key={p.id} className="border border-slate-800 rounded-xl p-5 bg-slate-900">
            <h3 className="font-bold text-lg text-white">{p.name}</h3>
            <p className="text-sky-400 font-semibold text-xl mt-2">
              ${p.sale_price ?? p.price}
            </p>
            <a href={`/products/${p.slug}`} className="block mt-4 text-center bg-sky-500 hover:bg-sky-400 text-slate-950 font-bold py-2 rounded-lg">
              View Details
            </a>
          </div>
        ))}
      </div>
    </div>
  );
}
Recipe 4 • Digital Goods & Licensing

Auto-Generating & Emailing Software License Keys

Problem: When a customer purchases software or plugins, you want to automatically mint a formatted license key (XXXX-XXXX-XXXX-XXXX) and email it to them.

PHP — License Key Generation & Automated Email
/**
 * Automatically mint a 16-character license key upon digital access grant.
 */
add_action('omnify_customer_access_granted', function($customer_id, $product_id) {
    // Format: XXXX-XXXX-XXXX-XXXX
    $raw_bytes   = random_bytes(8);
    $hex_string  = strtoupper(bin2hex($raw_bytes));
    $license_key = implode('-', str_split($hex_string, 4));

    // Retrieve customer details
    $customer = omnify()->container()->get(\Omnify\eCommerce\Repositories\Omnify_Customer_Repository::class)->find($customer_id);
    if (! $customer) {
        return;
    }

    // Store license key in customer options
    update_user_meta($customer['user_id'] ?? 0, "omnify_license_{$product_id}", $license_key);

    // Dispatch email to customer
    $subject = 'Your Software License Activation Key';
    $message = "Hello {$customer['first_name']},\n\n"
             . "Thank you for your order! Here is your official activation license key:\n\n"
             . "  {$license_key}\n\n"
             . "You can download your product files anytime from your customer account portal.\n\n"
             . "Best regards,\nThe Team";

    wp_mail($customer['email'], $subject, $message);
}, 10, 2);
Recipe 5 • Pricing & Promotions

Tiered Bulk Volume Discount Engine

Problem: You want an automated volume promotion: Buy 3+ items and get 10% off, buy 5+ items and get 20% off total cart value.

PHP — Volume Discount Application
/**
 * Automatically adjust cart subtotal based on volume quantity.
 */
add_filter('omnify_before_checkout_validation', function($data, $settings, $items) {
    $total_qty = 0;
    foreach ($items as $item) {
        $total_qty += (int) ($item['quantity'] ?? 1);
    }

    // Apply 20% discount for 5+ items, or 10% for 3+ items
    $discount_rate = 0.0;
    if ($total_qty >= 5) {
        $discount_rate = 0.20;
    } elseif ($total_qty >= 3) {
        $discount_rate = 0.10;
    }

    if ($discount_rate > 0) {
        $data['volume_discount_rate'] = $discount_rate;
    }

    return $data;
}, 10, 3);
Recipe 6 • Payment Gateways

Custom Offline Bank Wire Transfer / Direct Invoice Method

Problem: You need an offline "Wire Transfer / Invoice" payment method that puts orders in on-hold status until wire verification, displaying wire instructions to the customer.

PHP — Registering Custom Wire Transfer Method
/**
 * Set custom wire transfer orders to 'on-hold' status and append bank details.
 */
add_action('omnify_order_created', function($order_id, $order_data) {
    if (($order_data['payment_method'] ?? '') === 'wire_transfer') {
        $orders = omnify()->container()->get(\Omnify\eCommerce\Repositories\Omnify_Order_Repository::class);
        
        // Set status to 'on-hold' until bank funds clear
        $orders->update($order_id, ['status' => 'on-hold']);
        
        // Add wire instructions for customer view
        $orders->add_note(
            $order_id,
            'Bank: Chase Bank | Account: #987654321 | Routing: #123456789. Please mention Order #' . $order_id,
            customer_visible: true
        );
    }
}, 10, 2);
Recipe 7 • International Commerce

Dynamic Multi-Currency Pricing with Live Geo-Location

Problem: Show European customers prices in EUR (€) and UK customers prices in GBP (£) dynamically based on visitor IP address.

PHP — Geo-Currency Conversion via omnify_format_price
/**
 * Dynamically convert base USD amounts to EUR or GBP based on Cloudflare or IP headers.
 */
add_filter('omnify_format_price', function($formatted, $amount, $settings) {
    $country = $_SERVER['HTTP_CF_IPCOUNTRY'] ?? 'US';

    if (in_array($country, ['FR', 'DE', 'IT', 'ES', 'NL'], true)) {
        // EUR conversion rate (e.g. 1 USD = 0.92 EUR)
        $eur_price = number_format($amount * 0.92, 2);
        return "€{$eur_price} EUR";
    }

    if ($country === 'GB') {
        // GBP conversion rate (e.g. 1 USD = 0.79 GBP)
        $gbp_price = number_format($amount * 0.79, 2);
        return "£{$gbp_price} GBP";
    }

    return $formatted;
}, 10, 3);
Recipe 8 • Security & Digital Locker

Restricting Digital Downloads by Maximum Download Count

Problem: Prevent customers from sharing digital download links by capping file access to a maximum of 5 downloads per customer.

PHP — Download Quota Enforcement via wp_omnify_download_logs
/**
 * Enforce a strict 5-download limit per digital product file.
 */
add_filter('omnify_signed_download_expires_in', function($seconds, $file_id, $customer_id) {
    global $wpdb;
    $table = $wpdb->prefix . 'omnify_download_logs';

    // Count successful past downloads for this customer and file
    $download_count = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$table} WHERE customer_id = %d AND file_id = %d AND status = 'success'",
        $customer_id,
        $file_id
    ));

    if ($download_count >= 5) {
        // Return 0 to immediately invalidate and expire the link
        wp_die(__('You have reached the maximum allowed downloads (5/5) for this file. Please contact support.', 'my-store'), 403);
    }

    return $seconds;
}, 10, 3);