Promo Codes in Rails
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!