How can I test unpaid subscriptions in Stripe with Ruby on Rails? - ruby-on-rails

I'm trying to test the scenario in my Rails app where a customer has allowed a subscription to go to 'unpaid' (usually because the card expired and was not updated during the two weeks while Stripe retried the charge) and is finally getting around to updating the card and reactivating the account. I believe I have the logic correct (update the card and pay each unpaid invoice) but I'd like to be able to test it, or better yet, write some RSpec tests (a feature test and possibly a controller test). The problem is, I can't figure out how to create a mock situation in which a subscription is 'unpaid'. (I suppose I could create a bunch of accounts with expired cards and wait two weeks to test, but that's not an acceptable solution. I can't even change the subscription retry settings for only the 'test' context to speed up the process.) I found stripe-ruby-mock but I can't manually set the status of a subscription.
Here's what I've tried:
plan = Stripe::Plan.create(id: 'test')
customer = Stripe::Customer.create(id: 'test_customer', card: 'tk', plan: 'test')
sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id)
sub.status = 'unpaid'
sub.save
sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id)
expect(sub.status).to eq 'unpaid'
This was the result with stripe-ruby-mock:
Failure/Error: expect(sub.status).to eq 'unpaid'
expected: "unpaid"
got: "active"

Stripe's recommended procedure is:
Setup the customer with the card 4000000000000341 ("Attaching this card to a Customer object will succeed, but attempts to charge the customer will fail.")
Give the customer a subscription but with a trial date ending today/tomorrow.
Wait
Step three is annoying, but it'll get the job done.

Taking #VoteyDisciple's suggestion, I've worked through something to have this reasonably automated in RSpec ('reasonably', given the circumstances). I'm using VCR to capture the API calls (against the test Stripe environment), which is essential, as it means the sleep call only happens when recording the test the first time.
Using comments to indicate behaviour done via Stripe's API, as my implementation is rather caught up in my project, and that's not so useful.
VCR.use_cassette('retry') do |cassette|
# Clear out existing Stripe data: customers, coupons, plans.
# This is so the test is reliably repeatable.
Stripe::Customer.all.each &:delete
Stripe::Coupon.all.each &:delete
Stripe::Plan.all.each &:delete
# Create a plan, in my case it has the id 'test'.
Stripe::Plan.create(
id: 'test',
amount: 100_00,
currency: 'AUD',
interval: 'month',
interval_count: 1,
name: 'RSpec Test'
)
# Create a customer
customer = Stripe::Customer.create email: 'test#test.test'
token = card_token cassette, '4000000000000341'
# Add the card 4000000000000341 to the customer
customer.sources.create token: 'TOKEN for 0341'
# Create a subscription with a trial ending in two seconds.
subscription = customer.subscriptions.create(
plan: 'test',
trial_end: 2.seconds.from_now.to_i
)
# Wait for Stripe to create a proper invoice. I wish this
# was unavoidable, but I don't think it is.
sleep 180 if cassette.recording?
# Grab the invoice that actually has a dollar value.
# There's an invoice for the trial, and we don't care about that.
invoice = customer.invoices.detect { |invoice| invoice.total > 0 }
# Force Stripe to attempt payment for the first time (instead
# of waiting for hours).
begin
invoice.pay
rescue Stripe::CardError
# Expecting this to fail.
end
invoice.refresh
expect(invoice.paid).to eq(false)
expect(invoice.attempted).to eq(true)
# Add a new (valid) card to the customer.
token = card_token cassette, '4242424242424242'
card = customer.sources.create token: token
# and set it as the default
customer.default_source = card.id
customer.save
# Run the code in your app that retries the payment, which
# essentially invokes invoice.pay again.
# THIS IS FOR YOU TO IMPLEMENT
# And now we can check that the invoice wass successfully paid
invoice.refresh
expect(invoice.paid).to eq(true)
end
Getting card tokens in an automated fashion is a whole new area of complexity. What I've got may not work for others, but here's the ruby method, calling out to phantomjs (you'll need to send the VCR cassette object through):
def card_token(cassette, card = '4242424242424242')
return 'tok_my_default_test_token' unless cassette.recording?
token = `phantomjs --ignore-ssl-errors=true --ssl-protocol=any ./spec/fixtures/stripe_tokens.js #{ENV['STRIPE_PUBLISH_KEY']} #{card}`.strip
raise "Unexpected token: #{token}" unless token[/^tok_/]
token
end
And the javascript file that phantomjs is running (stripe_tokens.js) contains:
var page = require('webpage').create(),
system = require('system');
var key = system.args[1],
card = system.args[2];
page.onCallback = function(data) {
console.log(data);
phantom.exit();
};
page.open('spec/fixtures/stripe_tokens.html', function(status) {
if (status == 'success') {
page.evaluate(function(key, card) {
Stripe.setPublishableKey(key);
Stripe.card.createToken(
{number: card, cvc: "123", exp_month: "12", exp_year: "2019"},
function(status, response) { window.callPhantom(response.id) }
);
}, key, card);
}
});
Finally, the HTML file involved (stripe_tokens.html) is pretty simple:
<html>
<head>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
</head>
<body></body>
</html>
Put all of that together, and, well, it might work! It does for our app :)

Related

Trouble with rspec and expect change

I'm using rspec to test a Ruby terminal app. I can't seem to figure out to test for a change in a hash
I'm still trying to wrap my head around testing in general, so it's quite possible I'm misunderstanding multiple things in regards to testing.
I'm trying to test that when a charge is added to an instance of Account but the charge exceeds the accounts limit. The balance on the account doesn't change.
class Account
def apply_charge(card_holder_name, charge)
charge = int_dollar_input(charge)
account = find_account_by_card_holder(card_holder_name)
temp_balance = account[:balance] + charge
account[:balance] = account[:balance] + charge if temp_balance < account[:limit]
end
end
describe "Adding a charge to an account" do
context "GIVEN a charge amount that exceeds the account limit" do
let(:account) { subject.add_account("William", 5454545454545454,'$1000') }
it "will do nothing" do
expect(subject.apply_charge('William', '$1200')).
to_not change {account[:balance]}
end
end
end
account is a array of hash(s)
account.inspect = [{:card_holder_name=>"William", :card_number=>5454545454545454, :limit=>1000, :balance=>0}]
1) Account Adding a charge to an account GIVEN a charge amount that
exceeds the account limit will do nothing
Failure/Error:
expect(subject.apply_charge('William', '$1200')).
to_not change {account[:balance]}
expected `account[:balance]` not to have changed, but was not given a block
# ./spec/account_spec.rb:46:in `block (4 levels) in <top (required)>'
Expect needs to be in a block: expect { subject.apply_charge('William', '$1200') }.to.....

Can't use upsert on existing member

I'm trying to subscribe users to Mailchimp with Gibbon 2.2.4 with a generic subscribe method I've been using, and then shortly after I want to add in some extra fields to track the results of a quiz they took.
I want to store this data on Mailchimp because I'd like to manage the emails I send off directly from Mailchimp's dashboard.
The service I created to handle my subscriptions:
class MailchimpService
def subscribe(list_id,email,first_name)
GIBBON.lists(list_id).members.create({
body: {
email_address: email,
status: 'subscribed',
merge_fields: {
FNAME: first_name,
},
double_optin: false,
update_existing: true
}
})
end
def subscribe_to_quiz(first_name, email, user_id, quiz_id)
list_id = ENV['QUIZ_MAILCHIMP_LIST_ID']
if subscribe(list_id,email,first_name)
attempt = QuizAttempt.where("user_id = ? AND quiz_id = ?", user_id, quiz_id).last
correct = attempt.correct_answer_count
total = attempt.questions_answered
successful = attempt.successful?
send_quiz_results(list_id, email, correct, total, successful)
end
end
def send_quiz_results(list_id, email, correct, total, successful)
GIBBON.lists(list_id).members(email).upsert(
body: {
email_address: email,
status: 'subscribed',
merge_fields: {
correct_answers: correct,
total_answers: total,
successful: successful
},
update_existing: true
})
end
end
In subscribe_to_quiz, I'm subscribing the user to my quiz_list in Mailchimp. The values of the fields I'm updating here are irrelevant, but I think they're quite explanatory. When I try to run my upsert statement in send_quiz_results, I get the following error:
the server responded with status 400
#title="Member Exists",
#detail="foo#bar.baz is already a list member. Use PUT to insert or update list members.",
#body={"type"=>"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/", "title"=>"Member Exists", "status"=>400, "detail"=>"foo#bar.baz is already a list member. Use PUT to insert or update list members.", "instance"=>""},
#raw_body="{\"type\":\"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/\",\"title\":\"Member Exists\",\"status\":400,\"detail\":\"foo#bar.baz is already a list member. Use PUT to insert or update list members.\",\"instance\":\"\"}",
#status_code=400
I have no clue why it won't let me do this... It seems like it's referencing a create statement, but the extracted source for the error references my upsert statement.
I know I'm using the corresponding PUT verb for Gibbon, since the following is taken straight from the documentation:
Of course, body is only supported on create, update, and upsert calls. Those map to HTTP POST, PATCH, and PUT verbs respectively.
I have no clue why this isn't working... I've tried taking out the other fields and just simply throwing in the ones I'm updating. I've also tried running it straight from the terminal to make sure nothing's overlapping.
The MailChimp API docs show that when updating a member you must provide the member's subscriber_hash, which the MD5 hash of the lowercase version of the members email address.
Use Digest::MD5.hexdigest to hash the email address with MD5:
GIBBON.lists(list_id).members(Digest::MD5.hexdigest(email.downcase)).upsert

Access subscription details in Stripe payment

I have two subscription plans in my Ruby on Rails application. I use stripe webhook to email to customer when subscription has been created. In the email I want to store data about subscription (and plan) details e.g. when trial_end and plan name or price.
def webhook
stripe_event = Stripe::Event.retrieve(params[:id]) #retrieving Event ID
if stripe_event.type == "customer.subscription.created" #checks if retrieved Event type subscription is created
stripe_customer_token = stripe_event.data.object.customer # Get Customer ID
customer = Stripe::Customer.retrieve(stripe_customer_token) #here I'm able to retrieve Customer data e.g. customer.email
subscription = customer.subscriptions.first.id #according to documentation I need to retrieve Subscription by supplying its ID. I can retrieve Subscription, but don't understand how to retrieve its data, like: subscription.trial_end
UserMailer.customer_subscription_created(customer.email).deliver #this works well
UserMailer.customer_subscription_created(subscription.trial_end).deliver #this does not work
end
end
I have retrieved Subscription of my Customer. When I retrieve customer I can access my customer data like: customer.email I assumed I would be able to do the same when I retrieve Subscription: subscription.trial_end, but this gives me an error. How can I access Subscription data?
Besides when I change plan of a Subscription I do it like so and it works:
def change_user_plan(customer_id, subscription_id)
customer = Stripe::Customer.retrieve("#{customer_id}")
subscription = customer.subscriptions.retrieve("#{subscription_id}")
subscription.plan = 2
subscription.save
end
Here is link to Stripe API to retrieve Subscription
You are correct, you are able to do what you are trying to do. Once you have your subscription, subscription.trial_end works. I just tested it:
2.1.6 :013 > customer = Stripe::Customer.retrieve("#{customer_id}")
=> #<Stripe::Customer:0x3fcd1ed0a630 id=...> JSON: { ... }
2.1.6 :014 > subscription = customer.subscriptions.retrieve("#{subscription_id}")
=> #<Stripe::Subscription:0x3fcd1ecae574 id=...> JSON: { ... }
2.1.6 :015 > subscription.trial_end
=> 1438387199
The problem is this line:
subscription = customer.subscriptions.first.id
You are saving the subscription id itself. You need to do:
subscription = customer.subscriptions.first
to save the whole subscription. Also, you can use subscriptions.retrieve to supply the id for retrieval (as you are doing in your second code example).

Rails Delayed Job Continuously Running

I created a batch email system for my website. The problem I have, which is terrible, is it continuously sends out emails. It seems the job is stuck in an infinite loop. Please advise. It is crazy because on my development server only one email is sent per account, but on my production server I received 5 emails. Thus, meaning all users of my site received multiple emails.
Controller:
class BatchEmailsController < ApplicationController
before_filter :authenticate_admin_user!
def deliver
flash[:notice] = "Email Being Delivered"
Delayed::Job.enqueue(BatchEmailJob.new(params[:batch_email_id]), 3, 10.seconds.from_now, :queue => 'batch-email', :attempts => 0)
redirect_to admin_batch_emails_path
end
end
Job in the lib folder:
class BatchEmailJob < Struct.new(:batch_email_id)
def perform
be = BatchEmail.find(batch_email_id)
if be.to.eql?("Contractors")
cs = Contractor.all
cs.each do|c|
begin
BatchEmailMailer.batch_email(be.subject, be.message, be.link_name, be.link_path, be.to, c.id).deliver
rescue Exception => e
Rails.logger.warn "Batch Email Error: #{e.message}"
end
else
ps = Painter.all
ps.each do |p|
begin
BatchEmailMailer.batch_email(be.subject, be.message, be.link_name, be.link_path, be.to, p.id).deliver
rescue Exception => e
Rails.logger.warn "Batch Email Error: #{e.message}"
end
end
end
end
end
Delayed Job Initializer:
Delayed::Worker.max_attempts = 0
Please provide feedback on this approach. I want to send out the batch email to all users, but avoid retrying multiple times if something goes wrong. I added rescue block to catch email exceptions in hope that the batch will skip errors and continue processing. As a last resort do not run again if something else goes wrong.
What one of my apps does which seems to work flawlessly after millions of emails:
1) in an initializer, do NOT let DelayedJob re-attempt a failed job AND ALSO not let DJ delete failed jobs:
Delayed::Worker.destroy_failed_jobs = false
Delayed::Worker.max_attempts = 1
2) Scheduling a mass email is 1 job, aka the "master job"
3) When THAT jobs runs, it spawns N jobs where N is the number of emails being sent. So each email gets its own job. (Note: if you use a production email service with 'batch' capability, one "email" might actually be a batch of 100 or 1000 emails.)
4) We have an admin panel that shows us if we have any failed jobs, and if they are, because we don't delete them, we can inspect the failed job and see what happened (malformed email address etc)
If one email fails, the others are un-affected. And no email can ever be sent twice.

Rails 3: loops and plucking items out best practices

I am working on a small app that allows for users to add a product (or subscription) to their cart. Upon creating their account, the new user is sent to a "bundle" page where it asks if they would like to add a different subscription to a different product altogether for a bundled price.
Here is where I am stuck: Upon submitting the user's credit card info I get slightly "lost in translation" when trying to setup the bundle pricing to submit to Authorize.net (I understand how to authnet, not the question here).
Here is what I have so far:
current_order.products.includes(:client).each do |product|
transaction = current_order.submit_order_to_authnet(product)
if transaction.result_code == 'Ok'
new_group = Group.create!(:name => "#{current_user.full_name} #{product.title}", :type => 'school', :start_date => Time.now, :status => 'active', :site_id => 1)
primary = session[:primary_product_id].eql?(product.id) ? true : false
# Add subscription to Group
new_group.add_subscription(product, current_order, transaction.subscription_id, 'active', primary)
# Add Subscription to CurrentOrder
current_order.subscriptions << new_group.subscriptions.last
# Add user to NewGroup
current_user.groups << new_group
# Create New Group Admin
new_group.group_admins.create(:user_id => current_user.id)
# Send success email
OrderMailer.checkout_confirmation(current_user).deliver
else
errors << transaction.result_code
end
end
I am trying to figure out the best solution when it comes to looping through each product in the users current_order because the second subscription in the users cart is the subscription that gets the discount applied too. I know I can write something like this:
current_order.products.includes(:client).each do |product|
if current_order.products.many? and product == current_order.products.last
# run discount logic
else
# continue with authnet for single subscription
end
end
But I am just not sure if that is a best practice or not. Thoughts?
So the only subscription that doesn't get discounted is the first one? Why not write it like this:
current_order.products.includes(:client).each do |product|
if product == current_order.products.first
# continue with authnet for single subscription
else
# run discount logic
end
end

Resources