When I got tasked with adding promo codes in our Rails apps, the first thing that surprised me was how few posts there were on how to do it. So here I am to fill that gap.
First! We have to create a promo codes model:
class CreatePromoCodes< ActiveRecord::Migration
def change
create_table :promo_codes do |t|
t.string :code
t.decimal :amount
t.text :purpose
t.timestamps
end
end
end
And the model:
class PromoCode < ActiveRecord::Base
# ...
has_and_belongs_to_many :users
# ...
end
Then we should add some fields to a user to reference promo codes. For our case, we want to know what promo codes they have used in the past, how much credit they have, and what user referred them.
class AddFieldsToUser < ActiveRecord::Migration
def change
add_column :users, :credits, :decimal, default: 0.0
add_column :users, :referring_user_id, :integer
end
end
And the updates to the user model:
class User < ActiveRecord::Base
# ...
belongs_to :referring_user, class_name: "User", foreign_key: "referring_user_id"
has_and_belongs_to_many :promo_codes
# ...
end
Plus one final migration to allow the has_and_belong_to_many to work:
class CreatePromoCodeUser < ActiveRecord::Migration
def change
create_table :promo_codes_users, id: false do |t|
t.belongs_to :user, index: true
t.belongs_to :promo_code, index: true
t.timestamps
end
end
end
We also have a PromoCodesController. This isn't really doing anything unique, we just need it so we can allow admins to create promo codes.
class PromoCodesController < AdminController
before_action :set_promo_code, only: [:update, :destroy]
respond_to :html, only: [:index]
respond_to :json, except: [:index]
def index
@promo_codes = PromoCode.all
respond_with(@promo_codes)
end
def create
@promo_code = PromoCode.new promo_code_params
@promo_code.save
end
def update
@promo_code.update(promo_code_params)
end
def destroy
@promo_code.destroy
render nothing: true, status: 204
end
private
def set_promo_code
@promo_code = PromoCode.find(params[:id])
end
def promo_code_params
params.require(:promo_code).permit(:code, :amount, :active, :purpose)
end
end
We then added a route that was specific to adding a promo code (within the user controller). We post to this route whenever a user adds a promo code:
def add_promo_code
user = User.find(params[:user_id])
# We decided the easiest way to do referring users
# was to have codes that looked like this: MYCODE-265,
# with 265 being the id of the referring user.
# Not necessarily teh best solution for everyone!
promo_code, referring_user = params[:promo_code].split('-')
code = PromoCode.find_by_code(promo_code)
if code
if user.promo_codes.include?(promo_code)
message = 'This promo code has already been used.'
else
user.promo_codes << code
user.credits += code.amount
if referring_user && user.referring_user_id.nil? && User.find_by_id(referring_user).present?
user.referring_user_id = referring_user
end
if user.save
message = 'Promo code successfully used!'
else
message = 'Error using promo code. Please try again or contact customer service.'
end
end
else
message = 'Not a valid promo code.'
end
respond_to do |format|
format.js { flash[:notice] = message }
end
end
Then, whenever we are getting ready to charge a user, we add code like this:
if @user.credits > 0.0
if @user.credits >= @price
@meal_plan.price = 0.0
@user.credits = @user.credits - @price
else
@meal_plan.price -= @user.credits
@user.credits = 0.0
end
end
What do referring users get? For us, they get a credit when the person they referred makes their first purchase:
def charge_user(user)
if user.first_charge? && user.referring_user
user.referring_user.credits += PromoCode.find_by_code(@user.promo_codes.first).amount
user.referring_user.save
end
end
There are a few other smaller things, but that was the bulk. If anyone has any feedback or something they would do differently, I'd love to see it in the comments!