Skip to content

Instantly share code, notes, and snippets.

@r00k
Created November 13, 2012 21:08
Show Gist options
  • Save r00k/4068407 to your computer and use it in GitHub Desktop.
Save r00k/4068407 to your computer and use it in GitHub Desktop.
diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb
index eee7f0a..4023606 100644
--- a/app/helpers/mailer_helper.rb
+++ b/app/helpers/mailer_helper.rb
@@ -31,6 +31,14 @@ module MailerHelper
width: 100% !important;
eostyle
end
+
BEN: Holy tabs batman!
+ def bundle_total_style(amount)
+ if amount < 0
+ td_refund_total
+ else
+ td_bundle_total
+ end
+ end
def button_data_style
inline_style <<-eostyle
@@ -349,7 +357,69 @@ module MailerHelper
width: '100%'
}
end
-
+
+ def td_bundle_header_style
+ inline_style <<-eostyle
+ color: #1ebbf3;
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
+ font-size: 12px;
+ font-weight: 600;
+ text-align: center;
+ text-transform: uppercase;
+ eostyle
+ end
+
+ def td_bundle_row
+ inline_style <<-eostyle
+ border-top: 1px solid #ddd;
+ padding: 15px;
+ eostyle
+ end
+
+ def td_bundle_row_style
+ inline_style <<-eostyle
+ color: #555;
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ text-align: center;
+ eostyle
+ end
+
+ def td_bundle_refund_style
+ inline_style <<-eostyle
+ color: #e85d5b;
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ text-align: center;
+ eostyle
+ end
+
+ def td_bundle_total
+ inline_style <<-eostyle
+ background: #71cb3a;
+ padding: 15px;
+ eostyle
+ end
+
+ def td_refund_total
+ inline_style <<-eostyle
+ background: #e85d5b;
+ padding: 15px;
+ eostyle
+ end
+
+ def td_bundle_total_style
+ inline_style <<-eostyle
+ color: #FFF;
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ text-align: center;
+ eostyle
+ end
+
def td_nav
inline_style <<-eostyle
padding-top: 12px;
diff --git a/app/jobs/bundle_charged_job.rb b/app/jobs/bundle_charged_job.rb
new file mode 100644
index 0000000..b9508d0
--- /dev/null
+++ b/app/jobs/bundle_charged_job.rb
@@ -0,0 +1,14 @@
+class BundleChargedJob < Struct.new(:bundle_id, :last_4)
+ # Constants
+ PRIORITY = 4
+
+ def self.enqueue(bundle, credit_card)
+ Delayed::Job.enqueue new(bundle.id, credit_card.last_4), priority: PRIORITY
+ end
+
+ def perform
+ bundle = Bundle.find(bundle_id)
+ Mailer.bundle_charged(bundle, last_4).deliver
+ end
+end
+
diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb
index 00a68ec..d6b29e3 100644
--- a/app/mailers/mailer.rb
+++ b/app/mailers/mailer.rb
@@ -8,6 +8,14 @@ class Mailer < ActionMailer::Base
layout 'mailer', except: [:lead_created, :qr_code_card_created, :ticket_created]
+ def bundle_charged(bundle, last_4)
+ @bundle = bundle
+ @last_4 = last_4
+
+ mail subject: action_subject(last_4: last_4, amount: bundle.amount.format),
+ to: bundle.user.email
+ end
+
def claim_credit_added_confirmation(claim)
@claim = claim
@header = reminder_header(sponsor: claim.sponsor, value: claim.value)
diff --git a/app/models/bundle.rb b/app/models/bundle.rb
index 4ff49a7..acde4f3 100644
--- a/app/models/bundle.rb
+++ b/app/models/bundle.rb
@@ -33,6 +33,7 @@ class Bundle < ActiveRecord::Base
if charge_successful
close
+ enqueue_email
else
return false
end
@@ -49,6 +50,7 @@ class Bundle < ActiveRecord::Base
def charge_and_close
if charge_for_bundle(amount)
close
+ enqueue_email
end
end
@@ -67,7 +69,7 @@ class Bundle < ActiveRecord::Base
private
def active_credit_card
- user.credit_cards.with_state(:active).prioritized.first
+ @active_credit_card ||= user.credit_cards.with_state(:active).prioritized.first
end
def amount_plus_newest_order(newest_order)
@@ -82,6 +84,12 @@ class Bundle < ActiveRecord::Base
orders.where state: 'completed'
end
+ def enqueue_email
+ if eligible_for_bundling?
+ BundleChargedJob.enqueue self, active_credit_card
+ end
+ end
+
def should_charge_immediately?(newest_order)
BundleChargingPolicy.new(self, newest_order).should_charge?
end
diff --git a/app/models/preview.rb b/app/models/preview.rb
index 14635a4..61c35b9 100644
--- a/app/models/preview.rb
+++ b/app/models/preview.rb
@@ -1,5 +1,9 @@
if Rails.env.development?
class Preview < MailView
BEN: formatting issue.
+ def bundle_charged
+ Mailer.bundle_charged Bundle.first, '1234'
+ end
+
def claim_credit_added_confirmation
Mailer.claim_credit_added_confirmation Claim.first
end
diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml
index 3b5a661..951c643 100644
--- a/app/views/layouts/mailer.html.haml
+++ b/app/views/layouts/mailer.html.haml
@@ -16,8 +16,8 @@
}
p, li {
BEN: !important should really be a last resort, but I guess this isn't our domain to worry about.
- font-size: 20px !important;
- line-height: 24px !important;
+ font-size: 14px !important;
+ line-height: 18px !important;
}
p[class='footer'] {
@@ -34,6 +34,10 @@
font-size: 16px !important;
}
+ h1 {
+ font-size: 40px !important;
+ }
+
h2 {
font-size: 27px !important;
line-height: 30px !important;
diff --git a/app/views/mailer/bundle_charged.html.haml b/app/views/mailer/bundle_charged.html.haml
new file mode 100644
index 0000000..0ac8ae1
--- /dev/null
+++ b/app/views/mailer/bundle_charged.html.haml
@@ -0,0 +1,142 @@
+- content_for :css do
+ :css
+ @media only screen and (max-width: 480px) {
+ table[class="receipt"] {
+ margin: 40px auto !important;
+ }
+
+ table[class="receipt-table"] {
+ width: 100% !important;
+ }
+
+ table[class="bundle-row"] {
+ padding: 15px 10px !important;
+ }
+
+ td[class="receipt hide"] {
+ display: none !important
+ }
+
+ td[class="receipt date hide"] {
+ display: none !important
+ }
+
+ td[class="receipt"] {
+ font-size: 16px !important;
+ width: 56% !important;
+ }
+
+ td[class="receipt header"] {
+ font-size: 13px !important;
+ }
+
+ td[class="receipt charged header"] {
+ font-size: 14px !important;
+ }
+
+ td[class="receipt date"] {
+ font-size: 14px !important;
+ width: 17% !important;
+ }
+
+ td[class="receipt charged"] {
+ font-size: 16px !important;
+ width: 27% !important;
+ }
+ }
+
+%table{table_attributes, class: 'receipt-table'}
+ %tr
+ %td
+ %h1{style: [h1_style, 'font-size: 50px; margin: 20px 0 10px 0;'].join}
+ Card Charged
+
+ %p{style: [p_style, 'font-size: 16px; font-weight: 600;'].join}
+ #{pluralize(@bundle.orders.count, 'Transaction')},
+ #{@bundle.orders.first.created_at.strftime('%m/%d')} -
+ #{@bundle.orders.last.created_at.strftime('%m/%d')}
+
+ %table.receipt{table_attributes, style: 'border-top: 2px solid #1ebbf3; margin: 40px 0;'}
+ %tr
+ %td
+ %table.bundle-row{table_attributes, style: [td_bundle_row, 'border-top: none;'].join}
+ %tr
+ %td.receipt.header{style: [td_bundle_header_style, 'text-align: left;'].join,
+ width: '10%'}
+ Date
+ %td.receipt.header{style: [td_bundle_header_style, 'text-align: left;'].join,
+ width: '34%'}
+ Business
+ %td.receipt.hide{style: td_bundle_header_style, width: '17%'}
+ Spent
+ %td.receipt.hide{style: td_bundle_header_style, width: '5%'}
+ &nbsp
+ %td.receipt.hide{style: td_bundle_header_style, width: '17%'}
+ Saved
+ %td.receipt.charged.header{style: [td_bundle_header_style,
+ 'text-align: right;'].join, width: '17%'}
+ Charged
+
+ - @bundle.orders.each do |order|
+ %tr
+ %td
+ %table.bundle-row{table_attributes, style: td_bundle_row}
+ %tr
+ %td.receipt.date{style: [td_bundle_row_style,
+ 'color: #777; font-weight: 200; text-align: left;'].join, width: '10%'}
+ = order.created_at.strftime('%m/%d')
+ %td.receipt{style: [td_bundle_row_style, 'text-align: left;'].join,
+ width: '34%'}
+ = order.merchant.name.html_safe
+ %td.receipt.hide{style: [td_bundle_row_style, 'color: #999;'].join,
+ width: '17%'}
+ = order.total.format
+ %td.receipt.hide{style: [td_bundle_row_style,
+ 'color: #999; font-weight: 200;'].join, width: '5%'}
+ &mdash;
+ %td.receipt.hide{style: [td_bundle_row_style, 'color: #71cb3a;'].join,
+ width: '17%'}
+ = order.credit.format
+ %td.receipt.charged{style: [td_bundle_row_style, 'text-align: right;'].join,
+ width: '17%'}
+ = order.balance.format
+
+ - @bundle.refunds.each do |refund|
+ %tr
+ %td
+ %table.bundle-row{table_attributes, style: td_bundle_row}
+ %tr
+ %td.receipt.date{style: [td_bundle_refund_style,
+ 'font-weight: 200; text-align: left;'].join, width: '10%'}
+ = refund.created_at.strftime('%m/%d')
+ %td.receipt{colspan: '4', style: [td_bundle_refund_style,
+ 'text-align: left;'].join}
+ = refund.order.merchant.name.html_safe
+ %td.receipt.charged{style: [td_bundle_refund_style, 'text-align: right;'].join,
+ width: '17%'}
+ \-
+ = refund.order.balance.format
+
+ %tr
+ %td
+ %table.bundle-row{table_attributes, style: bundle_total_style(@bundle.amount)}
+ %tr
+ %td.receipt.date.hide{style: [td_bundle_total_style, 'text-align: left;'].join,
+ width: '10%'}
+ = @bundle.updated_at.strftime('%m/%d')
+ %td.receipt{colspan: '4', style: [td_bundle_total_style,
+ 'text-align: left;'].join}
+ Card *#{@last_4} #{@bundle.amount < 0 ? 'Refunded' : 'Charged'}
+ %td.receipt.charged{style: [td_bundle_total_style, 'text-align: right;'].join,
+ width: '17%'}
+ = @bundle.amount.format
+
+ - if @bundle.orders.sum(&:credit) > 0
+ %p.receipt{style: [p_style, 'margin: 40px 0 0 0;'].join}
+ By paying with LevelUp, you saved
+ $#{@bundle.orders.sum(&:credit).to_money}.
+
+ %p.receipt{style: [p_style, 'margin: 40px 0 20px 0;'].join}
+ We sometimes group transactions together in order to help businesses save on payment
+ processing fees. The money they save gets passed back to you as rewards, so it's a win-win!
+ Got questions? Email #{mail_to '[email protected]', support_email, style: link_style}.
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e8c5a54..4ded66b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -181,6 +181,9 @@ en:
Grab %{value} to spend on anything you want at %{merchant}. Enjoy!
mailer:
+ bundle_charged:
+ subject:
+ Your card ending in *%{last_4} has been charged %{amount}.
claim_credit_added_confirmation:
reminder_header:
You unlocked $%{value} from %{sponsor}!
diff --git a/db/schema.rb b/db/schema.rb
index b84e824..5ba7b05 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -169,9 +169,9 @@ ActiveRecord::Schema.define(:version => 20121107193833) do
t.string "campaign_type", :default => "customer_acquisition", :null => false
t.string "customer_type", :default => "any", :null => false
t.integer "funding_merchant_id", :null => false
BEN: Goddamnit Rails.
+ t.boolean "sharing_enabled", :default => true, :null => false
t.boolean "allow_external_claiming", :default => true, :null => false
t.boolean "claim_automatically_on_order", :default => false, :null => false
- t.boolean "sharing_enabled", :default => true, :null => false
t.text "internal_note"
end
diff --git a/spec/integration/bundle_closing_spec.rb b/spec/integration/bundle_closing_spec.rb
index b26a843..74bb4ba 100644
--- a/spec/integration/bundle_closing_spec.rb
+++ b/spec/integration/bundle_closing_spec.rb
@@ -22,6 +22,8 @@ describe 'bundling' do
context 'and its age is greater than the max age' do
context 'and its amount is greater than the max amount' do
before do
+ stub_braintree_credit_card_find_success
+
Timecop.travel(Time.now - age_over_max.days) do
@bundle = create(:bundle, user: @user)
create :completed_order, balance: amount_over_max, bundle: @bundle, user: @user
@@ -37,10 +39,17 @@ describe 'bundling' do
it 'needs to be closed' do
@policy.should_close?.should be_true
end
BEN: This test is duplicated. It'd be nice to pull it into a matcher or method.
+
+ it 'enqueues a job to email the bundle close receipt' do
+ jobs = Delayed::Job.where('handler LIKE ?', '%BundleChargedJob%')
+ jobs.should_not be_empty
+ end
end
context 'and its amount is less than the max amount' do
before do
+ stub_braintree_credit_card_find_success
+
Timecop.travel(Time.now - age_over_max.days) do
@bundle = create(:bundle, user: @user)
create :completed_order, balance: amount_under_max, bundle: @bundle, user: @user
@@ -58,12 +67,21 @@ describe 'bundling' do
it 'needs to be closed' do
@policy.should_close?.should be_true
end
+
+ it 'enqueues a job to email the bundle close receipt' do
+ @bundle.charge_and_close
+
+ jobs = Delayed::Job.where('handler LIKE ?', '%BundleChargedJob%')
+ jobs.should_not be_empty
+ end
end
end
context 'and its age is less than the max age' do
context 'and its amount is greater than the max amount' do
before do
+ stub_braintree_credit_card_find_success
+
Timecop.travel(Time.now - age_under_max.days) do
@bundle = create(:bundle, user: @user)
create :completed_order, balance: amount_over_max, bundle: @bundle, user: @user
@@ -79,6 +97,11 @@ describe 'bundling' do
it 'needs to be closed' do
@policy.should_close?.should be_true
end
+
+ it 'enqueues a job to email the bundle close receipt' do
+ jobs = Delayed::Job.where('handler LIKE ?', '%BundleChargedJob%')
+ jobs.should_not be_empty
+ end
end
context 'and its amount is less than the max amount' do
diff --git a/spec/integration/bundling_spec.rb b/spec/integration/bundling_spec.rb
index 3e5aef7..9501d2d 100644
--- a/spec/integration/bundling_spec.rb
+++ b/spec/integration/bundling_spec.rb
@@ -89,6 +89,8 @@ describe 'bundling' do
context 'when adding the new order exceeds the maximum bundle size' do
before do
+ stub_braintree_credit_card_find_success
+
create_bundle_for @user
place_order 10000000.to_money
end
diff --git a/test/unit/bundle_test.rb b/test/unit/bundle_test.rb
index 3cdbab5..7179824 100644
--- a/test/unit/bundle_test.rb
+++ b/test/unit/bundle_test.rb
@@ -44,31 +44,39 @@ class BundleTest < ActiveSupport::TestCase
context 'when the bundle should be charged for' do
setup do
- @bundle = create(:bundle, user: create(:user, :with_credit_card))
+ @user = create(:user, :with_credit_card, eligible_for_bundling: true)
+ @bundle = create(:bundle, user: @user)
@order = stub('order', balance: 5.to_money, :bundle= => nil)
+ stub_braintree_credit_card_find_success
stub_bundle_should_be_charged
- stub_customer_charger_success
end
- should 'try to charge for the bundle' do
- @bundle.add_order(@order)
- amount_to_charge = @bundle.amount + @order.balance
+ context 'when charging for the bundle succeeds' do
+ setup do
+ stub_customer_charger_success
+ @result = @bundle.add_order(@order)
+ end
- assert_received(CustomerCharger, :new) do |expect|
- expect.with(@bundle, amount_to_charge, instance_of(CreditCard))
+ should 'try to charge for the bundle' do
+ amount_to_charge = @bundle.amount + @order.balance
+
+ assert_received(CustomerCharger, :new) do |expect|
+ expect.with(@bundle, amount_to_charge, instance_of(CreditCard))
+ end
+
+ assert_received @customer_charger, :charge
end
- assert_received @customer_charger, :charge
- end
+ should 'return true' do
+ assert_equal true, @result
+ end
- should 'return true' do
- assert_equal true, @bundle.add_order(@order)
- end
+ should 'close the bundle' do
+ assert @bundle.closed?
+ end
- should 'close the bundle' do
- @bundle.add_order(@order)
- assert @bundle.closed?
+ should_enqueue_delayed_job 'BundleChargedJob'
end
context 'when charging for the bundle fails' do
@@ -85,6 +93,8 @@ class BundleTest < ActiveSupport::TestCase
should 'not close the bundle' do
assert [email protected]?
end
+
+ should_not_enqueue_delayed_job 'BundleChargedJob'
end
context 'when charging causes a BraintreeTimeout to be raised' do
@@ -110,12 +120,15 @@ class BundleTest < ActiveSupport::TestCase
assert_equal return_value, @bundle.add_order(@order)
end
end
+
+ should_not_enqueue_delayed_job 'BundleChargedJob'
end
end
end
context 'amount' do
setup do
+ stub_braintree_credit_card_find_success
stub_braintree_customer_sale_success
completed_order = create(:completed_order)
@@ -153,8 +166,10 @@ class BundleTest < ActiveSupport::TestCase
context 'charge_and_close' do
setup do
- @bundle = create(:bundle, user: create(:user, :with_credit_card))
BEN: I created a trait for being eligible for bundling that'd be handy here.
+ @user = create(:user, :with_credit_card, eligible_for_bundling: true)
+ @bundle = create(:bundle, user: @user)
+ stub_braintree_credit_card_find_success
stub_customer_charger_success
@bundle.charge_and_close
end
@@ -170,6 +185,8 @@ class BundleTest < ActiveSupport::TestCase
should 'close the bundle' do
assert @bundle.closed?
end
+
+ should_enqueue_delayed_job 'BundleChargedJob'
end
context 'close' do
diff --git a/test/unit/jobs/bundle_charged_job_test.rb b/test/unit/jobs/bundle_charged_job_test.rb
new file mode 100644
index 0000000..89dd200
--- /dev/null
+++ b/test/unit/jobs/bundle_charged_job_test.rb
@@ -0,0 +1,38 @@
+require 'test_helper'
+
+class BundleChargedJobTest < JobTest
+ context 'self.enqueue' do
+ setup do
+ stub_braintree_credit_card_find_success
+
+ @bundle = create(:bundle, :closed)
+ @credit_card = build_stubbed(:credit_card)
+ CreditCard.stubs(:find).with(@credit_card.id).returns(@credit_card)
+ BundleChargedJob.enqueue @bundle, @credit_card
+ end
+
+ should_enqueue_delayed_job 'BundleChargedJob' do
+ { bundle_id: @bundle.id, last_4: @credit_card.last_4}
+ end
+ end
+
+ context 'perform' do
+ setup do
+ stub_braintree_customer_sale_success
+
+ completed_order = create(:completed_order)
+ user = completed_order.user
+ @expected_email = user.email
+
+ @bundle = create(:bundle, user: user)
+ @bundle.orders << completed_order
+
+ BundleChargedJob.new(@bundle.id, '1234').perform
+ end
+
+ should have_sent_email.
+ with_subject(/Your card ending in .+ has been charged/).
+ to(@expected_email).with_body(/We sometimes group transactions together/)
+ end
+end
+
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment