Points and Rewards Plugin

Allow users to earn points from purchases and redeem them for discounts on future orders.

PHPWordPressJavaScriptMySQL

Overview

The points and rewards plugin allows customers to earn points from their purchases and actions on the site, which can then be redeemed for discounts on future orders. Points can be earned through actions such as making a purchase or leaving a review on a product.

Goals

We previously had a Points and Rewards plugin from a third-party developer running on the site, but it had grown outdated and needed to be replaced with an up-to-date custom solution. My goal was to replicate functionality and improve the user experience while creating something that could fit with the new design of the site.

I needed to create a plugin that could track points earned and handle redemption of those points for discounts at checkout. Points events needed to be tracked so that users and administrators could see the history of points earned and redeemed. I also needed to be able to migrate points events from the old plugin to the new one so that users did not lose their previously earned points.

Architecture

The plugin can be broken down into a few different sections: Points Management, WooCommerce Integration, Elementor Widgets, and Admin Pages.

Points Management

Points management is split between two classes, the Points_Manager and Points_Transaction_Log. These classes represent the core functionality of the plugin, and each has its own database table they use to store points data. The Points Manager class manages the user points table, storing the current points balance for each user. The manager is used to update, set, and retrieve points balances. The Transaction Log is used as the record of points transactions, storing the details of individual points events. I separated the current points balance from the transaction history so that the system could easily retrieve current balances while still maintaining an auditable record of how that balance was earned and spent.

WooCommerce Integration

These classes handle connecting the plugin to the WooCommerce functionality, such as checkout, coupons, and product reviews. The Checkout_Handler sets up AJAX requests for handling points redemptions by the user at checkout. It also handles the process of deducting and awarding points at different stages of the checkout/order creation process. For example, it deducts redeemed points when the order is placed and switched to “processing” status, awards points when an order is marked as “completed” status, and refunds spent points when an order is marked as “canceled” or “refunded” status.

The Coupon_Manager uses WooCommerce’s coupon system to create short-lived, single-use coupons for points redemptions. This makes it easy to apply discounts and log them on the order line items.

The Product_Review_Reward responds to the comment approval hook for an order review and awards points to the user who made the review.

These integration classes make use of the Points Manager and Transaction Log to apply points changes and track transactions with their event descriptions.

flowchart LR
    PR[Points Redemption] -->OC(Order Created)
    OC -->|Points Held/ Deducted| OS{Order Status}
    OS -->Comp[Completed] & Fail[Canceled/refunded]
    Comp -->Award[Award Points]
    Fail -->res[Restore Points]

Elementor Widgets

I knew that I was going to be using Elementor for the site redesign. So I created a few widgets that could be used when building the pages to allow myself to position and style the output wherever best fit the design. The previous plugin had used page hooks, such as “woocommerce_before_add_to_cart_button”, which restricted the positioning of the plugin output text to specific locations on the page. I created a widget for displaying on the single product page to show how many points a user could earn for purchasing that product, a widget for the checkout page that would let a user redeem points, and a widget for the account page that would display the user’s point balance and the recent transactions of their points. These widgets extended the Elementor widget system for customization and styling and made use of the point system classes to display relevant information to the user.

Admin Pages

For admin pages, I created a new settings page where admins could set the point award and redemption rates, as well as a tab to view and manage point balances for all users. I also extended the manual order entry to allow admins to redeem points on behalf of users when creating orders. We had a lot of phone orders, so this was a useful feature when a customer would want to redeem points on the order they were calling in.

Replacing the existing plugin meant that I needed to preserve the points and transactions that customers had already accumulated. As a tab on the admin settings page, I also created a tool to migrate the tables from the old plugin to the new ones that my plugin used. I set up a batch process to handle the migration of data from the old tables to the new ones, ensuring that the data was transferred accurately and efficiently as it was converted to my plugin’s table layouts.

Results

The plugin made for a seamless transition with the table migration, as I managed to complete the migration process during the site downtime of switching to Elementor for the site design and theming. With the widgets in place, users would have the same functionality as before, but with a more customizable and easier-to-maintain points plugin on the admin side.

What I Would Have Done Differently

If I were writing this plugin now, I would revisit the database design. This was one of my first projects involving SQL beyond basic queries, so with the experience I’ve gained since then, I would change how I structured the tables. I would drop some unnecessary fields and optimize the table layouts for better performance and indexing. I would also consider not using the WooCommerce coupons. While it integrated conveniently with WooCommerce’s existing discount system, generating a coupon for every redemption also meant managing those records in the WooCommerce coupon table. This led to needing to hide coupons marked as generated by the plugin to prevent cluttering up the admin coupon table view. I would instead make use of a virtual coupon system and apply the discount to the order directly.