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.
System Pipeline & Data Flow
Omnify processes requests cleanly through decoupled application layers:
Quickstart & Installation
Omnify is distributed as a clean, self-contained WordPress plugin with zero external PHP dependencies.
# 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
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
| 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.
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
| Table | Primary Function | Indices & Keys |
|---|---|---|
wp_omnify_products | Master catalog records (download, simple, variable, bundle) | PRIMARY (id), UNIQUE KEY (slug), KEY (status, type) |
wp_omnify_product_variations | Format matrix (SKU, price swatches, attributes) | PRIMARY (id), KEY (product_id) |
wp_omnify_product_files | Physical files, versions, and attachments | PRIMARY (id), KEY (product_id), KEY (expires_at) |
wp_omnify_product_categories | Category terms & taxonomy linkages | PRIMARY (id), KEY (product_id), KEY (slug) |
wp_omnify_product_brands | Product brands & manufacturer terms | PRIMARY (id), KEY (product_id), KEY (slug) |
wp_omnify_product_tags | Faceted search and discovery tags | PRIMARY (id), KEY (product_id), KEY (slug) |
wp_omnify_orders | Orders, financial totals, currency, and tax tracking | PRIMARY (id), UNIQUE KEY (order_number), KEY (customer_id, status) |
wp_omnify_order_items | Snapshot of purchased items, prices, and quantities | PRIMARY (id), KEY (order_id), KEY (product_id) |
wp_omnify_order_notes | Staff internal comments & customer visibility updates | PRIMARY (id), KEY (order_id), KEY (customer_visible) |
wp_omnify_customers | Customer CRM profiles, addresses, and tax exemptions | PRIMARY (id), UNIQUE KEY (email), KEY (user_id), KEY (status) |
wp_omnify_customer_notes | CRM support notes attached to customer accounts | PRIMARY (id), KEY (customer_id), KEY (customer_visible) |
wp_omnify_customer_activity | Audit trail (logins, checkout started, address changes) | PRIMARY (id), KEY (customer_id), KEY (activity_type) |
wp_omnify_customer_access | Digital file entitlements & access permissions | PRIMARY (id), UNIQUE KEY (customer_id, product_id) |
wp_omnify_downloads | Cryptographic download stream transaction logs | PRIMARY (id), KEY (product_id), KEY (file_id), KEY (customer_id) |
wp_omnify_coupons | Promotional codes, percentage/fixed rules, limits | PRIMARY (id), UNIQUE KEY (code) |
wp_omnify_reviews | Product ratings, content, and approval states | PRIMARY (id), KEY (product_id), KEY (status) |
wp_omnify_abandoned_carts | Captured checkout inputs, recovery tokens, reminders | PRIMARY (id), UNIQUE KEY (token), KEY (email, status) |
wp_omnify_wishlists | Customer wishlist favorite associations | PRIMARY (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)
$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)
$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)
$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)
$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
| Shortcode | Default Slug | Functionality |
|---|---|---|
[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.
// Request Headers
Accept: application/json
Content-Type: application/json
X-WP-Nonce: e9b41a29f8
...
REST API Reference
Namespace: /wp-json/omnify/v1
Storefront Public Endpoints
Administrative Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /orders | Paginated orders list with status, date, and customer filters. |
| GET | /orders/{id} | Detailed order information with line items and customer notes. |
| POST | /orders/{id}/mark-paid | Transitions order to paid, reduces stock, and grants downloads. |
| POST | /orders/{id}/trash | Soft-deletes order to trash. |
| POST | /orders/{id}/restore | Restores order from trash. |
| GET | /customers/{id}/activity | Retrieves audit timeline events for a customer. |
| POST | /access | Manually grants digital file access to a customer. |
| POST | /access/revoke | Revokes file download entitlements. |
Action Hooks Directory
Action hooks let you execute custom logic when specific events happen inside Omnify.
| Action Hook | Arguments | Fires When |
|---|---|---|
omnify_before_boot | $plugin, $container | Fires before Omnify registers internal services and repositories. |
omnify_booted | $plugin, $container | Fires after full bootstrap is complete. |
omnify_order_created | $order_id, $order_data | Fires immediately after an order is recorded in the database. |
omnify_order_status_changed | $order_id, $new_status, $old_status | Fires when an order transitions between statuses (e.g. pending → completed). |
omnify_customer_access_granted | $customer_id, $product_id, $expires_at, $id | Fires when digital product downloads are granted to a customer. |
omnify_customer_access_revoked | $customer_id, $product_id | Fires when access is revoked upon refund or cancellation. |
omnify_download_logged | $log_id, $product_id, $file_id, $customer_id, $status | Fires after a customer downloads a protected file. |
omnify_stock_reduced | $product_id, $variation_id, $qty, $new_stock | Fires after atomic stock reduction on checkout. |
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 Hook | Parameters | Return Type | Primary 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
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.
$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.
/**
* 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);
/**
* 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);
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.
$data (array) — Form submission keys (first_name, email, address, custom fields) • $settings (array) — Store options • $items (array) — Line items in cart.
/**
* 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);
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.
$error (null|\WP_Error) — Initial state • $data (array) — Validated checkout data • $settings (array) — Store settings • $items (array) — Line items.
/**
* 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);
/**
* 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);
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.
/**
* 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;
});
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.
/**
* 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);
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.
/**
* 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);
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.
/**
* 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.
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.
/**
* 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);
/**
* 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);
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.
/**
* 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);
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.
// 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>
);
}
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.
/**
* 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);
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.
/**
* 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);
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.
/**
* 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);
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.
/**
* 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);
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.
/**
* 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);