Description of functionality:
So I have a subscription based website that has two plans: Monthly and yearly. A monthly subscription is charged a monthly fee every month, while a yearly subscription is charged a one-time cumulative fee of 10 months, each year. ($29/mo and $290/yr)
I have 10% recurring referral discounts that are applied each month, for every active member who was referred (100% max). So if Bob signs up for a monthly subscription in October, and throughout the next month refers 5 people who sign up, he should have a 50% discount accumulated. This will be applied to his subscription fee in November, which will result in $14.50 for that month. But assume one of his referrals cancels in November. This will drop his discount for December by 10%, which will give him a 40% discount, with 4 active referrals, resulting in $17.40 for December.
Example of working code:
If every one of Bob's referrals is monthly, this is fairly simple to apply with webhooks. An amount is just deducted from Bob's current balance every time a successful payment goes through, if the user connected to that payment is one of Bob's referrals.
# Webhooks Controller
def payment_success
referred_user = User.find_by_customer_id(#subscription.customer.reference)
if referring_user = User.find_by_affiliate_code(referred_user.referral_code)
balance = referring_user.subscription.balance_in_cents
referring_user.subscription.adjustment({"amount_in_cents"=>"-290","memo"=>"Balance adjusted for referring #{referred_user.email}. Payment for referral was successful."})
referring_user.subscription.save
end
head :ok
end
This sends a request to Chargify, the payment processor I'm using, to adjust the balance accordingly.
My real problem:
It isn't this straightforward, though, when one of Bob's referrals is a yearly user.
Since the yearly subscription is a one-time payment, the payment_success webhooks aren't coming through every month, which means 10% of Bob's subscription can't be deducted each month from this yearly user's active referral.
I thought of just setting Bob's plan price to 10% less than standard, instead of adjusting the balance each month. This would require 9 different plans, though, since the plan price directly refers to a Plan object on Chargify, which points to its price attribute. That means that there would need to be a 90%_monthly_plan, a 80%_monthly_plan, and so on, which all live on the Chargify servers. This seems overly complex, and I also wouldn't be surprised if it caused other issues I'm not even considering at this point.
My current (unsatisfactory) solution:
So instead I decided to just create a scheduler on my server that is created when a yearly user signs up, and it fires every month.
I'm using rufus-scheduler for this. One important thing to note - Schedules are destroyed when the server is restarted, so I'm creating schedule records, and an initializer goes through these records and creates these schedules every time the server restarts.
# modified Webhooks Controller
def payment_success
referred_user = User.find_by_customer_id(#subscription.customer.reference)
if referring_user = User.find_by_affiliate_code(referred_user.referral_code)
balance = referring_user.subscription.balance_in_cents
referring_user.subscription.adjustment({"amount_in_cents"=>"-290","memo"=>"Balance adjusted for referring #{referred_user.email}. Payment for referral was successful."})
referring_user.subscription.save
if #subscription.product.handle == 'yearly'
schedule = ReferralSchedule.create(
type_of: "yearly discount",
referred_user_id: referred_user.id,
referring_user_id: referring_user.id,
time_to_execute: "30.41d"
)
ReferralScheduler::create_yearly_discounts_schedule(schedule)
end
end
head :ok
end
And the module handling the schedule creation:
module ReferralScheduler
def self.create_yearly_discounts_schedule(schedule)
referred_user = User.find(schedule.referred_user_id)
referring_user = User.find(schedule.referring_user_id)
# first_date is calculated to be the first date in the future, that the scheduler should run.
first_date = Time.now + (30.41-(DateTime.now - schedule.created_at.to_datetime).fdiv(30.41)%1*30.41).days
scheduler = Rufus::Scheduler.new
scheduler.every schedule.time_to_execute, first: first_date , last_at: (schedule.created_at+12.months) do
if referring_user && referred_user.subscription_status == :active
balance = referring_user.subscription.balance_in_cents
referring_user.subscription.adjustment({"amount_in_cents"=>"-290","memo"=>"Balance adjusted for referring #{referred_user.email}. Referral is still active."})
referring_user.subscription.save
end
end
scheduler.at (schedule.created_at+12.months) do
schedule.destroy!
end
end
end
And the initializer:
require 'rufus-scheduler'
ReferralSchedule.where(type_of: "yearly discount").each do |schedule|
ReferralScheduler::create_yearly_discounts_schedule(schedule)
end
I really hate using this method, as it doesn't seem very clean. It has also already screwed up my local server when I tried to run it at shorter intervals.
I also thought about using rake tasks and/or Grunt to perform this, but I have very limited knowledge on each of these, and I'm not sure if they will necessarily be easier/better solutions.
Any ideas?
One option might be to use the upcoming_renewal_notice webhook on Bob's subscription as a trigger to go see how many referrals he has made that are on annual plans, and apply any additional discounts.
https://docs.chargify.com/webhooks
upcoming_renewal_notice - A subscription set to renew within 3 days
Related
We have CreditCard related rake tasks that are supposed to be run on the 1st every month, to remind our clients to update their payment method if it expired at some point during the previous month.
class SomeJob < ApplicationJob
def perform
::CreditCard.
.where(expiration_date: Date.yesterday.all_month)
.find_each do |credit_card|
Email::CreditCard::SendExpiredReminderJob.perform_later(credit_card.id)
end
end
end
I'm concerned that this particular Job as we currently have it might not be Time zone safe due to the Date.yesterday.all_month we use to get last month's date range (remember the rake task is run on the 1st every month).
For example, if for some reason the Job were to be run past midnight (on the 2nd), it would incorrectly notify clients with cards expiring this month when it should of notified last month's expired cards.
The safest bet would be to substract more than 1 day from Date, but I'm not sure that's the cleanest way to go (and later on someone would not understand why we are substracting 5, 7 days or whatever).
Are there safer ways to do it?
Date.current.prev_month.all_month
.current may be better than .today because it checks if it is set time zone, see answer.
.prev_month or .months_ago(1) see https://apidock.com/rails/v3.2.13/Date/prev_month
I have a scenario where I would like to determine if a certain payment has been paid before a certain due date. The due date variable is just an integer from 1-30. Now this payment is supposed to be paid on a monthly basis so if the due date is 6th then every month on 6th I'm expecting the payment to be done. What I currently have is the following
DateTime.strptime(#payment.created.to_s,'%s') <= Date.new(Date.today.year, Date.today.month, #offer.due_date)
the offer model holds the due date and the payment comes in as a linux timestamp that's why the conversion. Now the problem with this simple logic is that it doesn' take into account the month at all. So DateTime.strptime(#payment.created.to_s,'%s') could be the payment for the last month and this would evaluate to true which would be not okay. How can I consider this logic, but on a monthly basis?
I'm creating a credit-based application, where the user puts credit in his account and the credit decreases over time based on user's active services.
For example:
User deposits U$ 30 in his account
User accquires a service that costs U$ 60/month
His credits will decrease over time, so in the second day of use, user's credit will be U$ 28 (U$ 30 - U$ 2 from the accquired service)
User will be able to see his current balance any time
I realize that I can use cron jobs to decrease the account balance, but it's gonna make a lot of requests to database and I am sure there must be better ways to do that.
How can I do it?
One way to achieve this is using a column and a callback e.g.
You will need to add a column say last_deducted_on as date then in the show action of account_balance you can have a before_action like
before_action :deduct_amount_if_applicable, only: :show
private
def deduct_amount_if_applicable
if current_user.deduction_applicable?
amt = (Date.today - account.last_deducted_on).to_i * amount_to_be_deducted
account.update_attribute(:total, (account.total - amt))
# update column with today's date
end
end
Although it will work, I suggest to use scheduler as it is a scheduled job and needs to be done every day, there are ways to reduce queries e.g. using update_all, etc. depending on use case but still the better option. In case if you have transaction history and you show date of transaction, above method will piss of user if he will open his account days later and suddenly huge amount is deducted.
I use stripe subscriptions for my users with one plan for, say, $10 per month for 100 API requests and that's a standard price. However, if the user has used 150 API requests, I charge them $10 + $3. For 200 and more requests it's $10 + $7.
Say, I've subscribed the user on October 9th. On November 9th the webhook event invoice.created will be called by stripe. So if the user has used 150 API requests, I have to add $3 more to the basic price (I can do that only within 1 hours according to Stripe documentation https://support.stripe.com/questions/metered-subscription-billing).
if even.type == 'invoice.created'
Stripe::InvoiceItem.create(
customer: stripe_customer_id,
invoice: event.data.object.invoice
amount: 300,
currency: 'usd',
description: 'additional price'
)
The questions:
Will the event invoice.created, indeed, be called on November 9th for the first time?
Will that additional price of $3 be added to the current invoice of $10 for October 9th-November 9th or will it be added to the future invoice for November 9th-December 9th? From the documentation it's not clear.
How do I add metadata to the original invoice of $10? I can add metadata to the additional invoice but in case the user has used less 100 API request I don't have to create the additional invoice at all, so I can't rely to the additional invoice.
It says
Create any invoice items before your customer is subscribed to the
plan, and then create the subscription via an update customer
subscription call.
but I subscribe the user once and forever on October 9th! How can I create an InvoiceItem before or on that (on October 9th) date if one month hasn't passed yet, and thus it's not known how many API calls the user makes (on October 9th the user has made, obviously, zero API calls)? I can only create InvoiceItems in 30 days because it's when I know how much to charge them on top on the $10! On October 9th I don't know that. Don't I understand anything?
Each time an invoice is created, you get the event invoice.created on your webhook. So when you subscribe a customer to a monthly plan on the 9th of November, you will get this event on the 9th of November, on the 9th of December, on the 9th of January, etc.
The first invoice for a subscription (generated when you create a subscription) is always closed immediately, so it is not possible to add an invoice item at that point. If that's something you want to do (for a setup fee for example) you need to create the invoice item before creating the subscription. That way it would get added automatically to the first invoice created when subscribing your customer.
In your case, you want to add fees to the next invoice based on the number of API requests your customer made during the month that just ended. So in the invoice.created event you need to detect whether it's a new month starting or the first one. If it's a new month, you then need to decide whether you have to add an Invoice Item to the user or not (based on the number of API requests).
You can't add metadata to the previous invoice from the month before. You just need to add the invoice item to the current invoice (for the month that is starting) and put a description indicating that the extra line item is for the consumption for the previous month.
We have a client site who is having gold jewelery magento shop. In that the jewel prices are daily changing,so he have to update each jewel items price often.
(The logic we have here is 1 gram price today is Rs. 1200 and tomorrow it may became RS. 1220 and client is selling 10 gram item as one jewel and 18 gram item as one jewel and so on, so he have to update the price daily for each jewel item based on its weight).
What we are doing now is having a script that checks and updates price daily based on one input field value from admin config section and cron job.
But what i am looking for is(Well truly its not me, but clients wants better than this solution) a way to automatic price change as like % price based on one gram value(Which can be updated by admin daily in configuration section as like now) daily basis. Is there anyway in magento for this? Please advice me.
Thanks
Ela