OLoyalty v1.0.0-alpha
A WooCommerce loyalty, rewards, and referrals engine. A real points ledger, member tiers with qualification rules, a reward catalog with cart, product, and gift-card payouts, store credit balances, and a fraud-aware referral program. Thirteen tables, idempotent ledger writes, REST + WP-CLI surface, native suite integration with OEngage, OMailer, OCart, OForms, OFeedback, OIntel.
What OLoyalty does
OLoyalty sits on top of WooCommerce. It tracks every point earned and spent in an append-only ledger, qualifies members into tiers based on configurable rules, ships a catalog of redeemable rewards (cart discounts, product unlocks, gift cards, store credit, custom payloads), runs a referral program with fraud scoring, and dispatches every change as a typed event for the rest of your stack to react to.
Getting installed
oloyalty_settings.points.enabled = false to record events without crediting points.oloyalty folder to /wp-content/plugins/ and activate via Plugins → Installed Plugins.{prefix}oloyalty_), seeds default tiers and earn rules, registers capabilities, declares HPOS and cart-checkout-blocks compatibility via WooCommerce's FeaturesUtil.oloyalty). Default settings are sensible for shadow-mode evaluation.Requirements
First program in 5 minutes
From fresh activation to a live loyalty program with one earn rule, one tier upgrade, and one redeemable reward.
points.value_per_point (default 0.01 = 1 cent per point) and points.expire_after_days (default 365).order.completed rule awards 1 point per $1 spent. Add a tier multiplier so Gold members earn 1.5x.cart_fixed, name "$10 off $50+", points cost 400, conditions cart_min_total 50.[oloyalty_balance], [oloyalty_rewards], and [oloyalty_referral] into the My Account page (or use the matching Gutenberg blocks).wp oloyalty member show {user_id} to confirm the points are flowing in after a test order.Plugin architecture
13 database tables
All prefixed with {prefix}oloyalty_. Created on activation through dbDelta. Hot queries are indexed: member-by-user_id, ledger-by-(member_id, created_at desc), giftcard-by-code, referral-by-referrer.
| Table | Purpose |
|---|---|
oloyalty_members | One row per enrolled customer. user_id UNIQUE, referral_code UNIQUE. Tracks points_balance, lifetime_earned, lifetime_redeemed, current tier_id, status (active / suspended / banned) |
oloyalty_ledger | Append-only points journal. type (earn / redeem / adjust / expire / reverse / transfer), points delta, balance_after, source, source_id, expires_at. Indexed (member_id, created_at) |
oloyalty_tiers | Tier definitions per program: name, rank, qualification_rules JSON, perks JSON, color, icon |
oloyalty_rewards | Reward catalog. type ENUM (cart_pct / cart_fixed / product_pct / product_fixed / free_product / free_ship / gift_card / store_credit / custom), points_cost, value JSON, conditions JSON |
oloyalty_redemptions | One row per redeemed reward. code UNIQUE, status (pending / applied / consumed / reversed), order_id, points_spent, issued_at, consumed_at |
oloyalty_giftcards | Gift card records. code UNIQUE, initial_amount, balance, currency, issued_to_email, issued_via (purchase / redemption / manual / bulk / api), status, expires_at |
oloyalty_giftcard_transactions | Per-card transaction log. type (issue / redeem / refund / adjust / void), amount, balance_after, optional order_id |
oloyalty_credit_balances | Store credit per member per currency. UNIQUE (member_id, currency) |
oloyalty_credit_transactions | Store credit ledger: type (issue / redeem / refund / adjust / expire), amount, balance_after, source |
oloyalty_referrals | Referral records. referrer_member_id, referred_email, referred_user_id, referred_order_id, status (invited / signed_up / purchased / rewarded / flagged / rejected / reversed), fraud_score, fraud_signals JSON |
oloyalty_earn_rules | Rule definitions. action (e.g. order.completed, review.left, birthday), points, formula JSON, multipliers JSON, caps JSON, exclusions JSON |
oloyalty_campaigns | Promotional campaigns. type (multiplier / bonus / win_back / holiday), config JSON, starts_at, ends_at, status |
oloyalty_audit_log | Every admin action with action, object_type, object_id, diff JSON, IP, user_id, timestamp |
Admin interface
Top-level menu OLoyalty (slug oloyalty) with capability-scoped submenus. Built with the same admin chrome as the rest of the Orravo suite for first-party feel.
| Submenu | Slug | Capability |
|---|---|---|
| Dashboard | oloyalty | oloyalty_manage |
| Members | oloyalty-members | oloyalty_manage_members |
| Points & Ledger | oloyalty-points | oloyalty_manage_members |
| Tiers | oloyalty-tiers | oloyalty_manage |
| Rewards | oloyalty-rewards | oloyalty_manage_rewards |
| Gift Cards | oloyalty-giftcards | oloyalty_manage_rewards |
| Store Credit | oloyalty-credit | oloyalty_manage_rewards |
| Referrals | oloyalty-referrals | oloyalty_manage_referrals |
| Campaigns | oloyalty-campaigns | oloyalty_manage |
| Reports | oloyalty-reports | oloyalty_view_reports |
| Integrations | oloyalty-integrations | oloyalty_manage |
| Settings | oloyalty-settings | oloyalty_manage |
Settings reference
All settings stored in wp_options under key oloyalty_settings as a single nested array. Access via OLoyalty_Settings::get('group.key').
points
| Key | Default | Description |
|---|---|---|
value_per_point | 0.01 | Currency value of one point. Used for liability snapshots |
expire_after_days | 365 | Earned points expire after this many days. 0 disables expiry |
allow_negative_balance | false | Whether redemptions can take a member negative |
shadow_mode | true (alpha) | Capture events but do not credit / debit. Flip off for live payouts |
referrals
| Key | Default | Description |
|---|---|---|
referrer_points | 500 | Points awarded to the referrer on conversion |
friend_points | 250 | Points awarded to the referred friend |
fraud_threshold | 0.7 | Score at or above this routes the referral to manual review |
min_friend_order_total | 10 | Minimum first-order subtotal for the conversion to count |
tiers
| Key | Default | Description |
|---|---|---|
recalc_cron | daily | How often the tier engine re-evaluates membership thresholds |
downgrade_grace_days | 30 | Days a member can drop below threshold before being demoted |
Points engine
Earn rules describe how members accumulate points. The ledger is append-only: every change writes a row with the new balance_after, so balances are auditable end to end. Adjustments, expirations, and reversals are first-class types alongside earn / redeem.
Default earn rules (seeded)
| Action | Points | Notes |
|---|---|---|
order.completed | 1 per $1 | Tier multiplier applied. Cap 2,000/day per member |
account.created | 100 | One-shot signup bonus |
review.left | 50 | Cap 1/day per member |
birthday | 250 | Auto-fired by daily birthdays sweep |
referral.converted | 500 | Awarded to the referrer on friend's first qualifying order |
Ledger types
earn: positive points from a triggering eventredeem: negative points spent on a rewardadjust: manual admin adjustment, positive or negativeexpire: negative points removed by the expiry sweep cronreverse: rollback of a prior earn or redeem (e.g. order refunded)transfer: balance moved between members (rare, requires capability)
Member tiers
Tiers are ordered by rank. Qualification rules are a JSON document evaluated against the member's lifetime stats. Perks JSON is read by the points engine, rewards engine, and any custom code that calls OLoyalty_Tiers::perks_for($member).
| Tier | Default qualification | Default perks |
|---|---|---|
| Bronze (rank 1) | Always | 1x point earn |
| Silver (rank 2) | 500 lifetime points | 1.25x earn, free shipping at $50+ |
| Gold (rank 3) | 2,000 lifetime points | 1.5x earn, priority support, early access |
| Platinum (rank 4) | 5,000 lifetime points | 2x earn, birthday bonus, quarterly gift, priority support |
oloyalty/tier/perks filter. Override qualification with oloyalty/tier/qualifies for full programmatic control.Rewards catalog
A reward is a row in oloyalty_rewards with a typed payout and an optional points cost. Redemption issues a single-use code (UNIQUE) that maps cleanly onto a WooCommerce coupon, gift card, store credit issuance, or any custom payload your site cares about.
| Reward type | Payout |
|---|---|
cart_pct | Percentage off the entire cart |
cart_fixed | Fixed amount off the entire cart |
product_pct | Percentage off a specific product or category |
product_fixed | Fixed amount off a specific product or category |
free_product | A specific product added free with the next order |
free_ship | Free shipping on the next order |
gift_card | Issues a row in oloyalty_giftcards with the configured amount |
store_credit | Credits the member's credit_balances row |
custom | Fires oloyalty/reward/issued with arbitrary value JSON for your own handler |
Gift cards
Gift cards have a UNIQUE code, fixed initial amount, and configurable expiry. Issuance writes a row in oloyalty_giftcards plus an issue transaction. Each redemption appends a redeem transaction with the new balance, so the audit trail is complete.
Code format
Default format is strtoupper(bin2hex(random_bytes(8))). Override with the oloyalty/giftcard/code_format filter to plug in your own scheme (Luhn-checksummed, prefix-by-merchant, etc).
Statuses
pending: scheduled for delivery in the futureactive: delivered, balance > 0, ready to redeemredeemed: fully drawn down to balance 0void: voided by an admin (capability-gated)expired: pastexpires_at, balance frozen
Store credit
Store credit lives on the member record (UNIQUE per member + currency). It auto-applies at checkout and never has a code, so it can never leak. Every change writes a row in oloyalty_credit_transactions with the new balance.
| Type | Use |
|---|---|
issue | Manual admin issuance, or from a reward redemption |
redeem | Auto-applied at checkout against the member's order total |
refund | Issued back when an order is refunded after credit was used |
adjust | Manual correction with admin note |
expire | Removed by an expiry sweep (off by default) |
Referral program
On enrollment each member gets a UNIQUE referral_code. Friends apply the code at checkout (or land via ?ref=CODE). The conversion handler runs fraud scoring first, and a score at or above referrals.fraud_threshold (default 0.7) routes the referral to manual review.
Fraud signals (configurable)
ip_match: referrer and friend share an IPua_match: identical user agentemail_disposable: friend's email is on a disposable-domain listvelocity: more than N referrals from the same referrer in 24hbilling_match: shared billing address
oloyalty/referral/fraud_score filter. The filtered float is clamped to 0.0..1.0 and stored in fraud_score; signals are persisted as JSON in fraud_signals for review.WooCommerce integration
OLoyalty hooks WooCommerce's order lifecycle directly. Points are awarded on woocommerce_order_status_completed and woocommerce_order_status_processing; reversed on refund or cancellation; and the review-points rule listens for comment_post on product comments.
| WC hook | OLoyalty handler |
|---|---|
woocommerce_order_status_completed | Award order.completed points (with tier + campaign multipliers) |
woocommerce_order_status_processing | Same as completed (configurable; off by default for cards-on-file flows) |
woocommerce_order_status_refunded | Reverse the original earn entry (type=reverse) |
woocommerce_order_status_cancelled | Reverse the original earn entry |
comment_post | Apply review.left rule when a product gets a comment |
woocommerce_cart_calculate_fees | Auto-apply store credit balance against the cart total |
FeaturesUtil::declare_compatibility('custom_order_tables')). Cart-checkout-blocks compatible (declared via cart_checkout_blocks).Shortcodes & blocks
Three shortcodes plus three matching Gutenberg blocks. All render the current member's data with no configuration required.
[oloyalty_balance]
[oloyalty_balance show_tier="false" show_progress="true"]
[oloyalty_rewards]
[oloyalty_rewards type="cart_fixed,gift_card" affordable_only="true"]
[oloyalty_referral]
[oloyalty_referral show_code="true" share_buttons="email,whatsapp,x"]
oloyalty/balance, oloyalty/rewards, oloyalty/referral. Same attributes as the shortcodes; configurable in the block inspector.Suite integrations
First-party bridges to other Orravo plugins. Each is togglable in Settings → Integrations and runs zero-config when both plugins are active.
| Integration | Behavior |
|---|---|
| OEngage | Every loyalty point earned also awards XP via do_action('oengage_award_xp', $user_id, $xp, 'oloyalty.points_earned'). The oloyalty/oengage/xp_per_point filter controls the conversion ratio (default 1:1). |
| OMailer | Triggers transactional sends on oloyalty.giftcard_issued, oloyalty.reward_issued, oloyalty.tier_changed. Templates ship in the box and can be overridden per-program. |
| OCart | Renders the rewards selector inside the OCart funnel and applies redemption codes against the OCart cart context. |
| OForms | Referral signup form auto-links the entry to the referrer's code. Form submissions can fire any earn rule. |
| OFeedback | Rate-after-redeem trigger fires the OFeedback widget after a redemption is consumed. |
| OIntel | Streams every loyalty event into the OIntel analytics warehouse for dashboards and segmentation. |
REST API
Namespace oloyalty/v1. Auth via standard WordPress nonces (admin-side) or application passwords (server-to-server). All routes return JSON. The /openapi endpoint serves a live OpenAPI 3.0 spec for codegen.
| Method | Route | Purpose |
|---|---|---|
| GET | /ping | Health check |
| GET | /openapi | OpenAPI 3.0 spec for the namespace |
| GET | /members | List enrolled members (paginated, filterable) |
| GET | /members/{id} | Get a single member with current balances and tier |
| GET | /members/me | Current authenticated user's member record |
| POST | /members/{id}/adjust | Manually adjust points balance (capability-gated) |
| GET | /members/{id}/ledger | Paginated ledger entries (filter by type, source, date) |
| POST | /members/{id}/ban | Suspend or ban the member |
| POST | /members/{id}/unban | Lift a suspension |
| GET | /tiers | List tier definitions |
| POST | /tiers/recalculate | Force a full tier recalc (long-running, runs async) |
| GET | /rewards | List active rewards in the catalog |
| POST | /rewards/{id}/redeem | Redeem a reward for the current member |
| GET | /giftcards | List issued gift cards |
| GET | /giftcards/{code} | Look up a gift card by code |
| POST | /giftcards/{code}/redeem | Apply a gift card to an order |
| POST | /giftcards/{code}/void | Void a gift card (capability-gated) |
| GET | /credit/{member_id} | Get store credit balances for a member |
| POST | /credit/{member_id}/issue | Issue store credit (capability-gated) |
| GET | /referrals | List referral records |
| POST | /referrals/{id}/approve | Approve a flagged referral |
| POST | /referrals/{id}/reject | Reject a flagged referral |
| GET | /reports/liability | Outstanding-points liability snapshot |
Developer hooks
OLoyalty ships a typed event bus (OLoyalty_Events::dispatch) that mirrors every emission through do_action. You can subscribe to any event with vanilla add_action.
Canonical events
oloyalty/booted: fired once after the engine wires upoloyalty/member/enrolled: a new member row was createdoloyalty/points/earned: points awarded (member, ledger entry)oloyalty/points/redeemed: points spent on a rewardoloyalty/points/expired: expiry sweep removed pointsoloyalty/points/adjusted: manual admin adjustmentoloyalty/tier/changed: member moved between tiersoloyalty/giftcard/issued: a gift card was createdoloyalty/giftcard/redeemed: balance reduced by a redemptionoloyalty/giftcard/voided: admin voided a cardoloyalty/giftcard/delivered: scheduled delivery completedoloyalty/credit/issued: store credit issuedoloyalty/credit/redeemed: store credit applied to an orderoloyalty/referral/created: a new referral row was insertedoloyalty/referral/converted: friend completed first qualifying orderoloyalty/referral/flagged: fraud score above thresholdoloyalty/liability/recalculated: liability snapshot completed
Filters
oloyalty/earn/eligible: gate whether an event qualifies for earn rulesoloyalty/earn/points: override the computed point valueoloyalty/earn/campaign_multiplier: override the active campaign multiplieroloyalty/ledger/allow_negative: permit a redemption to take a member negativeoloyalty/tier/qualifies: override tier qualification logicoloyalty/tier/qualifies_rules: rewrite qualification rules per evaluationoloyalty/tier/perks: rewrite the perks list returned for a tieroloyalty/redeem/eligible: gate reward redemption eligibilityoloyalty/redeem/value: override the cart-side discount value at apply timeoloyalty/giftcard/code_format: override gift card code generationoloyalty/referral/fraud_score: plug in your own fraud scoreroloyalty/oengage/xp_per_point: ratio of OEngage XP awarded per loyalty pointoloyalty/liability/value_per_point: override the per-point currency valueoloyalty/launcher/suppress: hide the storefront launcher widgetoloyalty/free_tier/show_branding: render the small "Powered by Orravo" mark on free tier
Helper functions
Procedural helpers for the common stuff. Wraps the underlying classes so themes and snippets stay readable.
| Function | Purpose |
|---|---|
oloyalty_award_points($user_id, $points, $source, $ctx = []) | Award points and write a ledger entry |
oloyalty_get_balance($user_id) | Return the current points balance |
oloyalty_get_member($user_id) | Return the member row, or null if not enrolled |
oloyalty_enroll($user_id, $args = []) | Enroll a customer; idempotent |
oloyalty_redeem_reward($user_id, $reward_id) | Redeem a reward and return the redemption row |
oloyalty_issue_giftcard($args) | Issue a gift card (amount, recipient, expiry) |
oloyalty_get_credit($user_id, $currency = 'USD') | Return the store credit balance |
oloyalty_referral_url($user_id) | Build a shareable referral URL |
oloyalty_log($message, $context = []) | Structured leveled logger |
WP-CLI
Eight wp oloyalty commands cover member, points, tiers, giftcard, credit, referral, report, and reward operations. Wire them into your deploy pipeline or cron jobs.
$ wp oloyalty member list --tier=gold --format=table
→ 6 gold-tier members listed
$ wp oloyalty points adjust 42 +500 --reason="customer service credit"
→ ledger #4912 written · balance 4,820 → 5,320 · ok
$ wp oloyalty tiers recalc
→ recalculated 30 members · 2 promotions · 0 demotions · 14 unchanged
$ wp oloyalty giftcard issue --to=adaeze@velluma.com --amount=25
→ issued GC-3F8A1C90 · $25.00 · expires 2027-05-07 · email queued
$ wp oloyalty credit issue --member=42 --amount=10
→ credit_transactions #318 · balance 0.00 → 10.00
$ wp oloyalty referral approve 14
→ referral #14 approved · referrer +500 · friend +250
$ wp oloyalty report liability
→ 30 members · 142,840 pts outstanding · $1,428.40 liability @ $0.01/pt
$ wp oloyalty reward list --status=active
→ 10 active rewards · 22,400 pts spent (30d window)
Admin capabilities
Six area capabilities so loyalty operations can be split across roles. Granted to the administrator role on activation; reassign to your own roles via standard add_cap.
| Capability | Grants |
|---|---|
oloyalty_manage | Top-level admin: settings, tiers, campaigns, integrations |
oloyalty_manage_members | Members & ledger CRUD, manual adjustments |
oloyalty_manage_rewards | Rewards catalog, gift cards, store credit |
oloyalty_manage_referrals | Referrals approval, rejection, fraud review |
oloyalty_view_reports | Reports + liability snapshots, read-only |
oloyalty_export | CSV exports of members, ledger, redemptions |
Liability reporting
Outstanding loyalty points are a real liability on your books. The liability snapshot computes total outstanding points, multiplies by your configured value_per_point, and stores the result with a timestamp so you can chart it over time.
GET /wp-json/oloyalty/v1/reports/liability
{
"snapshot_at": "2026-05-07T14:42:00Z",
"members_with_balance": 22,
"points_outstanding": 142840,
"value_per_point": 0.01,
"currency": "USD",
"estimated_liability": 1428.40,
"by_tier": {
"bronze": { "members": 12, "points": 4820, "estimated": 48.20 },
"silver": { "members": 8, "points": 14620, "estimated": 146.20 },
"gold": { "members": 4, "points": 38400, "estimated": 384.00 },
"platinum": { "members": 2, "points": 85000, "estimated": 850.00 }
}
}
oloyalty/liability/recalc (default daily). Override value_per_point per-snapshot via the oloyalty/liability/value_per_point filter.vs. the loyalty landscape
| Feature | WC Points & Rewards | YITH Points & Rewards | Smile.io (SaaS) | OLoyalty |
|---|---|---|---|---|
| Points engine with full ledger | ✓ | ✓ | ✓ | ✓ |
| Member tiers with qualification rules | ✓ | ✓ | ✓ | ✓ |
| Gift cards (issue, schedule, void) | paid add-on | paid add-on | paid add-on | ✓ |
| Store credit balances per member | partial | paid add-on | paid add-on | ✓ |
| Referral program with fraud scoring | paid add-on | paid add-on | paid add-on | ✓ |
| Campaign boosts (multipliers, win-back) | paid add-on | paid add-on | partial | ✓ |
| Liability snapshot + reporting | no | partial | no | ✓ |
| HPOS + cart-checkout blocks compatible | ✓ | ✓ | ✓ | ✓ |
| REST API + WP-CLI surface | partial | partial | partial | ✓ Full |
| Native suite integration (CRM + email) | no | no | partial | ✓ OEngage + OMailer |
| Audit log of every admin action | no | no | no | ✓ |
| Pricing model | $249/yr stack | $129/yr stack | SaaS · per-order | $99 once |
One purchase. Lifetime updates.
No tiers based on member count. No per-redemption tax. Pay once, install on the sites in your tier, get every future update free, including the v1.0 stable release.
- 1 production WooCommerce site
- All 13 tables
- Full REST + WP-CLI surface
- Email support · 48hr
- Up to 10 production sites
- All 13 tables
- Priority support · 24hr
- White-label admin labels
- Free onboarding call
- Unlimited production sites
- All features
- Same-day priority support
- Referral + campaign starter pack
- Custom integration sprint
What's shipped
- Points engine with append-only ledger and balance_after on every row
- Five seeded earn rules (order, account, review, birthday, referral)
- Earn rule formula, multipliers, caps, exclusions all configurable
- Tier engine with 4 default tiers and configurable qualification
- Tier perks JSON read by points engine and rewards engine
- Reward catalog with 9 reward types (cart, product, gift, credit, custom)
- Single-use redemption codes (UNIQUE on
oloyalty_redemptions.code) - Gift cards (issue, schedule delivery, redeem, void, transactions)
- Store credit balances per member per currency
- Referral program with per-member
referral_codeand fraud scoring - Campaign boosts (multiplier, bonus, win_back, holiday)
- Liability snapshots with per-tier breakdown
- Birthday bonus daily sweep cron
- Points expiry sweep cron
- Three shortcodes plus matching Gutenberg blocks
- HPOS + cart-checkout blocks compatibility declared via FeaturesUtil
- Typed event bus (
OLoyalty_Events) over do_action - Six area capabilities
- REST surface under
oloyalty/v1with OpenAPI spec - WP-CLI: 8
wp oloyaltycommands - Audit log of every admin action
- Suite bridges: OEngage (XP), OMailer (transactionals)
- Suite bridges: OCart (funnel rewards), OForms (referral signup)
- Suite bridges: OFeedback (rate-after-redeem), OIntel (analytics)
- 13 custom DB tables, full uninstall cleanup
- Visual rule builder (no-JSON earn rules)
- WooCommerce Points & Rewards importer (round-trip safe)
- YITH Points & Rewards importer
- Multi-program support (different programs per brand)
- Tier-based perks engine v2 (programmatic perk catalog)
- Multi-currency store credit auto-conversion
- Per-tier role assignment
- Documentation: full PHP-Doc surface coverage
Got a question about OLoyalty?
Reach out directly. Kenneth replies within 24 hours.
