Lucky Wheel
Lucky Wheel / Spin to Win
Interactive HTML5 canvas prize wheel with server-side outcome determination. Works in two modes: product-tied (quick setup on any raffle product) and standalone (dedicated admin page, usable anywhere in your store — cart, checkout, thank-you, popup, or shortcode).
Two Ways to Create a Wheel
| Product Wheel | Standalone Wheel | |
|---|---|---|
| Created via | Product edit screen → PRO Features | WooCommerce → Lucky Wheels → Add New |
| Tied to a product | Yes — one raffle product | No — independent |
| Display on product page | Automatic (before form, after form, or after summary) | Via Display Rules or shortcode |
| Cart / Checkout / Thank-you | No | Yes |
| Popup / Modal | No | Yes |
| Email gate | No | Yes |
| Coupon scope | Auto-restricted to that product | Configurable — any product, category, or store-wide |
| Shortcode | [raffle_wheel id="PRODUCT_ID"] | [raffle_wheel id="WHEEL_ID"] |
Product Wheel (Quick Setup)
Best for raffle owners who want a wheel on their product page with minimal configuration.
- Edit a raffle product.
- In the PRO Features section, check Lucky Wheel.
- Choose Quick setup (configure slices inline) or Use existing wheel (link a standalone wheel).
Standalone Wheel
Best for store-wide marketing: cart abandonment recovery, checkout incentives, lead generation, or post-purchase re-engagement.
- Go to WooCommerce → Lucky Wheels → Add New.
- Give it a title (e.g. "Summer Sale Spinner").
- Configure slices, display rules, coupon settings, and spin limits.
- Publish.
Configuring Slices
Each slice of the wheel represents one possible outcome. A wheel needs at least 2 slices.
| Field | Description |
|---|---|
| Color | Visual color on the wheel (color picker) |
| Label | Text displayed on the slice (e.g., "10% Off", "Free Ticket!") |
| Weight | Probability weight. Higher number = more likely. Weights are relative to all slices. |
| Prize type | No prize, Discount %, Discount (fixed), Free ticket, Custom message |
| Prize value | The value: percentage, currency amount, or custom text |
Free Ticket (standalone wheels)
When using a standalone wheel, the Free Ticket prize type shows an additional Target Product dropdown. This lets you select which product the 100% discount coupon applies to. If left empty, the coupon has no product restriction.
Default slices
When enabling a wheel for the first time, these defaults are pre-populated:
| Label | Type | Weight | Effect |
|---|---|---|---|
| 10% Off | Discount % | 20 | 10% coupon |
| Try Again | No prize | 35 | Better luck next time |
| 25% Off | Discount % | 10 | 25% coupon |
| Better Luck | No prize | 30 | Better luck next time |
| Free Ticket! | Free ticket | 5 | 100% discount coupon |
| 5% Off | Discount % | 25 | 5% coupon |
Display Rules (Standalone Wheels)
Configure where standalone wheels appear automatically, without shortcodes.
Product pages
| Option | Behaviour |
|---|---|
| Specific products | Select one or more products. The wheel renders on those product pages. |
| All products in categories | Select categories. The wheel renders on every product in those categories. |
| Do not show | The wheel does not auto-display on any product page. |
WooCommerce pages
| Option | Behaviour |
|---|---|
| Cart page | Renders above the cart. Works with both classic (shortcode) and block-based cart. |
| Checkout page | Renders above the checkout form. Works with both classic and block-based checkout. |
| Thank-you page | Renders on the order confirmation page. Supports a minimum order total threshold — the wheel only shows if the order meets the minimum. |
Block & Classic compatibility
All placements detect whether WooCommerce is using the block-based cart/checkout (default since WooCommerce 8.3) or the legacy shortcode pages and hook in correctly for both.
Popup / Modal Mode
Show the wheel as a popup triggered by user behavior.
Settings
| Setting | Description |
|---|---|
| Enable popup | Toggle on/off |
| Trigger | Delay (seconds after page load), Scroll (% scrolled), Exit intent (mouse leaves viewport) |
| Delay | Seconds to wait (for delay trigger) |
| Scroll % | Percentage of page scrolled (for scroll trigger) |
| Page scope | All pages, WooCommerce pages only, or Shop & product pages only |
| Cooldown | Days before the popup shows again to the same visitor (stored in localStorage) |
How it works
- The popup renders as a native HTML
<dialog>element in the page footer. - The selected trigger fires on the appropriate event.
- The dialog opens with a backdrop overlay and entrance animation.
- The close button (×) or clicking outside dismisses it.
- After closing, the cooldown prevents it from showing again for the configured number of days.
Coupon Settings
All auto-generated coupons share these characteristics:
- Single-use (1 redemption)
- Configurable expiry (default: 7 days, overridable per wheel or via global settings)
- Optional minimum order amount
- Described as "Lucky Wheel prize — [wheel name]"
Coupon scope (standalone wheels)
| Scope | Behaviour |
|---|---|
| Store-wide | No product/category restriction |
| Specific product | Coupon only valid for the selected product |
| Specific category | Coupon only valid for products in the selected category |
Auto-cleanup
Expired wheel-generated coupons are automatically trashed by a daily WP-Cron job. This can be toggled on/off in the global Pro settings.
Email Gate & Lead Generation
Require visitors to enter their email address before they can spin. Turns the wheel into a lead generation tool.
Enabling
- Edit a standalone wheel.
- In the Spin Limits meta box, check Require email before spinning.
- Optionally enable the consent checkbox and customize the consent text (e.g. "I agree to receive marketing emails").
How it works
- The wheel renders with an email form overlay instead of the Spin button.
- The visitor enters their email (and ticks consent if required).
- The email is validated and checked for duplicates (same email cannot spin the same wheel twice).
- On success, the overlay disappears and the Spin button becomes active.
- If the visitor wins a coupon, the coupon code is emailed to them using WooCommerce's branded email templates.
GDPR consent
When enabled, a checkbox with custom text appears below the email input. The form cannot be submitted without ticking it. Consent status is recorded in the database.
Integration hooks
After an email is collected, the action rfwc_pro_wheel_email_collected fires with four parameters:
do_action( 'rfwc_pro_wheel_email_collected', $email, $wheel_id, $row_id, $consent );
Use this to integrate with Mailchimp, Mailpoet, ConvertKit, or any other email service.
Admin: Collected Emails
Go to WooCommerce → Lucky Wheels → Collected Emails to view all collected emails.
| Feature | Description |
|---|---|
| Filter by wheel | Dropdown to show emails for a specific wheel |
| Pagination | 30 emails per page |
| CSV export | One-click download of all emails (or filtered by wheel) |
Thank-You Page / Post-Purchase
Show the wheel on the order confirmation page as a re-engagement tool: "You just ordered — spin for a reward on your next purchase!"
Order-aware features
| Feature | Description |
|---|---|
| Purchase threshold | Only show the wheel if the order total meets a configurable minimum |
| One spin per order | Prevents refresh abuse — each order can only spin each wheel once (tracked in order meta) |
| Customer-tied coupons | Won coupons are email-restricted to the customer's billing email |
| Raffle-aware mode | If the order contains raffle products with their own wheel, that product-specific wheel shows first |
Sound Effects
When enabled, the wheel plays sounds using the Web Audio API (no external files):
| Event | Sound |
|---|---|
| Spinning | Tick-tick as the pointer passes each slice |
| Win | Ascending fanfare |
| Lose | Descending tone |
Spin Tracking
| User type | Storage |
|---|---|
| Logged-in users | User meta (_rfwc_pro_wheel_spins) |
| Guests | WooCommerce session |
| Thank-you page | Order meta (_rfwc_pro_order_spun_wheels) |
Shortcode
[raffle_wheel id="123"]
| Attribute | Values | Default | Description |
|---|---|---|---|
id | Post ID | Required | Product ID (for product wheels) or Wheel ID (for standalone wheels) |
Global Pro Settings
Go to WooCommerce → Settings → Raffle → Pro to configure store-wide defaults.
| Setting | Default | Description |
|---|---|---|
| Default Max Spins | 1 | Max spins per user for new wheels (0 = unlimited) |
| Default Coupon Expiry | 7 days | How long wheel coupons last |
| Default Sound Effects | On | Enable sound by default |
| Auto-delete Expired Coupons | On | Daily cron to trash expired wheel coupons |
| Default Email Gate | Off | Require email by default |
How Spinning Works (Technical)
- Customer clicks Spin! (or submits email first if email gate is active).
- An AJAX request is sent to the server with the wheel/product ID (and order ID if on thank-you page).
- The server validates: wheel exists, user hasn't exceeded max spins, email gate passed, order not already spun.
- A winning slice is picked using weighted random selection (
random_int— cryptographically secure). - If the prize requires a coupon, a WooCommerce coupon is created with the configured settings.
- The server returns the winning slice index and a rotation angle.
- The frontend animates the wheel (4.5 second easing animation) to land on the winning slice.
- A result card appears showing the outcome — with a copy-to-clipboard button for coupon codes.
Prize Types
| Type | What happens |
|---|---|
| No prize | "Better luck next time" message. No action taken. |
| Discount % | A single-use WooCommerce coupon with the specified percentage off. |
| Discount (fixed) | A single-use fixed-amount coupon. |
| Free ticket | A 100% discount coupon for 1 unit (product wheels: that product; standalone: configurable target product). |
| Custom message | Displays the configured text. No coupon generated. Useful for "Come back tomorrow" or "Follow us on Instagram" type prizes. |
CSS Classes
Wheel
| Element | CSS Class |
|---|---|
| Container | .rfwc-pro-lucky-wheel |
| Title | .rfwc-pro-lucky-wheel__title |
| Canvas container | .rfwc-pro-lucky-wheel__container |
| Canvas | .rfwc-pro-lucky-wheel__canvas |
| Pointer | .rfwc-pro-lucky-wheel__pointer |
| Spin button | .rfwc-pro-lucky-wheel__spin-btn |
| Result container | .rfwc-pro-lucky-wheel__result |
| Win card | .rfwc-pro-lucky-wheel__result-card--win |
| Lose card | .rfwc-pro-lucky-wheel__result-card--lose |
| Coupon code | .rfwc-pro-lucky-wheel__coupon-code |
Email Gate
| Element | CSS Class |
|---|---|
| Container | .rfwc-pro-wheel-email-gate |
| Prompt text | .rfwc-pro-wheel-email-gate__prompt |
| Form wrapper | .rfwc-pro-wheel-email-gate__form |
| Email input | .rfwc-pro-wheel-email-gate__input |
| Consent label | .rfwc-pro-wheel-email-gate__consent |
| Submit button | .rfwc-pro-wheel-email-gate__submit |
| Error message | .rfwc-pro-wheel-email-gate__error |
Popup
| Element | CSS Class |
|---|---|
| Dialog | .rfwc-pro-wheel-popup |
| Inner wrapper | .rfwc-pro-wheel-popup-inner |
| Close button | .rfwc-pro-wheel-popup-close |
Files Reference
| File | Purpose |
|---|---|
class-rfwc-pro-lucky-wheel.php | Core wheel class — product mode, rendering, AJAX spin, prize generation |
class-rfwc-pro-lucky-wheel-cpt.php | Standalone wheel CPT registration, admin columns |
class-rfwc-pro-lucky-wheel-metaboxes.php | Edit screen meta boxes (slices, display rules, coupon, limits, shortcode) |
class-rfwc-pro-lucky-wheel-placements.php | Auto-display on cart/checkout/thank-you (block + classic), popup rendering |
class-rfwc-pro-lucky-wheel-email-gate.php | Email collection, custom table, AJAX, coupon emailing, admin page |
class-rfwc-pro-settings.php | Pro global settings (WooCommerce → Settings → Raffle → Pro) |
lucky-wheel.js | Frontend canvas drawing, animation, spin AJAX, email gate JS |
admin-lucky-wheel.js | Admin slice repeater, source toggle, prize type toggle |
lucky-wheel-popup.js | Popup trigger logic (delay, scroll, exit intent), localStorage cooldown |
lucky-wheel.css | Frontend wheel + email gate styles |
lucky-wheel-popup.css | Popup dialog styles |
admin.css | Admin meta box styles |
Action & Filter Hooks
| Hook | Type | Parameters | Description |
|---|---|---|---|
rfwc_pro_wheel_email_collected | Action | $email, $wheel_id, $row_id, $consent | Fires after email is collected (email gate) |
rfwc_pro_wheel_spin_completed | Action | $wheel_id, $prize_data, $session_email | Fires after a standalone wheel spin completes |