Created
November 13, 2012 21:08
-
-
Save r00k/4068407 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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%'} | |
+   | |
+ %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%'} | |
+ — | |
+ %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