saucy 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -28,6 +28,12 @@ After you bundle, run the generators:
28
28
 
29
29
  You will want to include the `ensure_active_account` `before_filter` in any controller actions that you want to protect if the user is using an past due paid account.
30
30
 
31
+ You will want to custom the from email address on billing emails:
32
+
33
+ Saucy::Configuration.mailer_sender = "billingemail@example.com"
34
+
35
+ In addition, there are a number of string such as application name, support url, etc. that are provided and customized with i18n translations. You can customize these in your app, and you can see what they are by looking at config/locales/en.yml in saucy.
36
+
31
37
  Development environment
32
38
  -----------------------
33
39
 
@@ -0,0 +1,20 @@
1
+ class BillingMailer < ActionMailer::Base
2
+ def receipt(account, transaction)
3
+ @account = account
4
+ @transaction = transaction
5
+ mail(:to => account.customer.email,
6
+ :subject => I18n.t(:subject,
7
+ :scope => [:saucy, :mailers, :billing_mailer, :receipt],
8
+ :default => "Subscription receipt"),
9
+ :from => Saucy::Configuration.mailer_sender)
10
+ end
11
+
12
+ def problem(account, transaction)
13
+ @account = account
14
+ mail(:to => account.customer.email,
15
+ :subject => I18n.t(:subject,
16
+ :scope => [:saucy, :mailers, :billing_mailer, :problem],
17
+ :default => "Problem with subscription billing"),
18
+ :from => Saucy::Configuration.mailer_sender)
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ <p>Unfortunately there was a problem processing your latest payment for your <%= @account.name -%> account on <%= t("app_name") %>.</p>
2
+
3
+ <p>Some functionality of your <%= @account.name -%> account on <%= t("app_name") %> is now disabled.</p>
4
+
5
+ <p>Please verify and fix the credit card details <%= link_to "here", account_billing_url(@account) %> to restore full access to the account.</p>
6
+
7
+ <p>If you have any questions, please contact us anytime at <%= mail_to t("support_email"), t("support_email") %>.</p>
8
+
9
+ <hr/>
10
+
11
+ <p>Thanks for your business,<br/>
12
+ The <%= t("app_name") %> Team<br/>
13
+ <%= t("app_url") %></p>
@@ -0,0 +1,14 @@
1
+ Unfortunately there was a problem processing your latest payment for your <%= @account.name -%> account on <%= t("app_name") %>.
2
+
3
+ Some functionality of your <%= @account.name -%> account on <%= t("app_name") %> is now disabled.
4
+
5
+ Please verify and fix the credit card details at the following url to restore full access to the account:
6
+ <%= account_billing_url(@account) %>
7
+
8
+ If you have any questions, please contact us anytime at <%= t("support_email") %>.
9
+
10
+ ============================================================
11
+
12
+ Thanks for your business,
13
+ The <%= t("app_name") %> Team
14
+ <%= t("app_url") %>
@@ -0,0 +1,41 @@
1
+ <p><strong>Thanks for your business!</strong></p>
2
+
3
+ <p>The latest payment for your <strong><%= @account.name -%> account on <%= t("app_name") %></strong> has been approved. This is only a receipt, no payment is due. If you have any questions, please contact us anytime at <%= mail_to t("support_email"), t("support_email") %></p>
4
+
5
+ <hr/>
6
+
7
+ <table>
8
+ <tr>
9
+ <td>
10
+ RECEIPT
11
+ </td>
12
+ <td>
13
+ <%= @transaction.created_at.to_s(:short_date) -%>
14
+ </td>
15
+ </tr>
16
+ <tr>
17
+ <td colspan="2">
18
+ Plan: <%= @account.plan.name -%></br>
19
+ Charge: USD <%= number_to_currency @account.plan.price -%>*<br/>
20
+ Credit card: XXXX-XXXX-XXXX-<%= @account.credit_card.last_4 %>
21
+
22
+ <p><small>* EU customers: prices exclude VAT</small></p>
23
+ </td>
24
+ </tr>
25
+ <tr>
26
+ <td>
27
+ <%= t("saucy.billing_address") %>
28
+ </td>
29
+ <td>
30
+ <strong>Bill to:</strong><br/>
31
+ <%= @account.name %><br/>
32
+ <%= @account.credit_card.cardholder_name %>
33
+ </td>
34
+ </tr>
35
+ </table>
36
+
37
+ <hr/>
38
+
39
+ <p>Thanks for your business,<br/>
40
+ The <%= t("app_name") %> Team<br/>
41
+ <%= link_to t("app_url"), t("app_url") %></p>
@@ -0,0 +1,25 @@
1
+ Thanks for your business!
2
+
3
+ The latest payment for your <%= @account.name -%> account on <%= t("app_name") %> has been approved. This is only a receipt, no payment is due. If you have any questions, please contact us anytime at <%= t("support_email") %>.
4
+
5
+ ============================================================
6
+ RECEIPT <%= @transaction.created_at.to_s(:short_date) -%>
7
+
8
+
9
+ Plan: <%= @account.plan.name -%>
10
+ Charge: USD <%= number_to_currency @account.plan.price -%>*
11
+ Credit card: XXXX-XXXX-XXXX-<%= @account.credit_card.last_4 %>
12
+
13
+ <%= t("saucy.billing_address") %>
14
+
15
+ Bill to:
16
+ <%= @account.name %>
17
+ <%= @account.credit_card.cardholder_name %>
18
+
19
+ * EU customers: prices exclude VAT
20
+
21
+ ============================================================
22
+
23
+ Thanks for your business,
24
+ The <%= t("app_name") %> Team
25
+ <%= t("app_url") %>
@@ -0,0 +1,6 @@
1
+ en:
2
+ app_name: App Name
3
+ app_url: 'http://example.com'
4
+ support_email: support@example.com
5
+ saucy:
6
+ billing_address: Billing Address
@@ -197,6 +197,11 @@ module Saucy
197
197
  account.subscription_status = account.subscription.status
198
198
  account.next_billing_date = account.subscription.next_billing_date
199
199
  account.save!
200
+ if account.past_due?
201
+ BillingMailer.problem(account, account.subscription.transactions.last).deliver!
202
+ else
203
+ BillingMailer.receipt(account, account.subscription.transactions.last).deliver!
204
+ end
200
205
  end
201
206
  end
202
207
  end
@@ -3,8 +3,10 @@ require 'saucy/layouts'
3
3
  module Saucy
4
4
  class Configuration
5
5
  attr_reader :layouts
6
+ cattr_accessor :mailer_sender
6
7
 
7
8
  def initialize
9
+ @@mailer_sender = 'donotreply@example.com'
8
10
  @layouts = Layouts.new
9
11
  end
10
12
  end
@@ -4,6 +4,10 @@ require "rails"
4
4
  module Saucy
5
5
  class Engine < Rails::Engine
6
6
  config.saucy = Configuration.new
7
+
8
+ rake_tasks do
9
+ load "saucy/railties/tasks.rake"
10
+ end
7
11
  end
8
12
  end
9
13
 
@@ -31,6 +31,10 @@ class FakeBraintree
31
31
  failure["errors"] ||= { "errors" => [] }
32
32
  { "message" => failure["message"], "verification" => { "status" => failure["status"], "processor_response_text" => failure["message"], "processor-response-code" => failure["code"], "gateway_rejection_reason" => "cvv", "cvv_response_code" => failure["code"] }, "errors" => failure["errors"], "params" => {}}
33
33
  end
34
+
35
+ def self.generated_transaction
36
+ {"status_history"=>[{"timestamp"=>Time.now, "amount"=>"50.00", "transaction_source"=>"CP", "user"=>"copycopter", "status"=>"authorized"}, {"timestamp"=>Time.now, "amount"=>"50.00", "transaction_source"=>"CP", "user"=>"copycopter", "status"=>FakeBraintree.transaction[:status]}], "created_at"=>Time.now, "currency_iso_code"=>"USD", "settlement_batch_id"=>nil, "processor_authorization_code"=>"ZKB4VJ", "avs_postal_code_response_code"=>"I", "order_id"=>nil, "updated_at"=>Time.now, "refunded_transaction_id"=>nil, "amount"=>"50.00", "credit_card"=>{"last_4"=>"1111", "card_type"=>"Visa", "token"=>"8yq7", "customer_location"=>"US", "expiration_year"=>"2013", "expiration_month"=>"02", "bin"=>"411111", "cardholder_name"=>"Chad Lee Pytel"}, "refund_id"=>nil, "add_ons"=>[], "shipping"=>{"region"=>nil, "company"=>nil, "country_name"=>nil, "extended_address"=>nil, "postal_code"=>nil, "id"=>nil, "street_address"=>nil, "country_code_numeric"=>nil, "last_name"=>nil, "locality"=>nil, "country_code_alpha2"=>nil, "country_code_alpha3"=>nil, "first_name"=>nil}, "id"=>"49sbx6", "merchant_account_id"=>"Thoughtbot", "type"=>"sale", "cvv_response_code"=>"I", "subscription_id"=>FakeBraintree.transaction[:subscription_id], "custom_fields"=>"\n ", "discounts"=>[], "billing"=>{"region"=>nil, "company"=>nil, "country_name"=>nil, "extended_address"=>nil, "postal_code"=>nil, "id"=>nil, "street_address"=>nil, "country_code_numeric"=>nil, "last_name"=>nil, "locality"=>nil, "country_code_alpha2"=>nil, "country_code_alpha3"=>nil, "first_name"=>nil}, "processor_response_code"=>"1000", "refund_ids"=>[], "customer"=>{"company"=>nil, "id"=>"108427", "last_name"=>nil, "fax"=>nil, "phone"=>nil, "website"=>nil, "first_name"=>nil, "email"=>"cpytel@thoughtbot.com"}, "avs_error_response_code"=>nil, "processor_response_text"=>"Approved", "avs_street_address_response_code"=>"I", "status"=>FakeBraintree.transaction[:status], "gateway_rejection_reason"=>nil}
37
+ end
34
38
  end
35
39
 
36
40
  ShamRack.at("www.braintreegateway.com", 443).sinatra do
@@ -109,6 +113,6 @@ ShamRack.at("www.braintreegateway.com", 443).sinatra do
109
113
 
110
114
  post "/merchants/:merchant_id/transactions/advanced_search" do
111
115
  # "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<search>\n <ids type=\"array\">\n <item>49sbx6</item>\n </ids>\n <created-at>\n <min type=\"datetime\">2011-01-10T14:14:26Z</min>\n </created-at>\n</search>\n"
112
- [200, { "Content-Encoding" => "gzip" }, ActiveSupport::Gzip.compress("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<credit-card-transactions type=\"collection\">\n <current-page-number type=\"integer\">1</current-page-number>\n <page-size type=\"integer\">50</page-size>\n <total-items type=\"integer\">1</total-items>\n <transaction>\n <id>49sbx6</id>\n <status>#{FakeBraintree.transaction[:status]}</status>\n <type>sale</type>\n <currency-iso-code>USD</currency-iso-code>\n <amount>50.00</amount>\n <merchant-account-id>Thoughtbot</merchant-account-id>\n <order-id></order-id>\n <created-at type=\"datetime\">2011-01-11T14:08:12Z</created-at>\n <updated-at type=\"datetime\">2011-01-11T14:08:12Z</updated-at>\n <customer>\n <id>108427</id>\n <first-name nil=\"true\"></first-name>\n <last-name nil=\"true\"></last-name>\n <company nil=\"true\"></company>\n <email>cpytel@thoughtbot.com</email>\n <website nil=\"true\"></website>\n <phone nil=\"true\"></phone>\n <fax nil=\"true\"></fax>\n </customer>\n <billing>\n <id nil=\"true\"></id>\n <first-name nil=\"true\"></first-name>\n <last-name nil=\"true\"></last-name>\n <company nil=\"true\"></company>\n <street-address nil=\"true\"></street-address>\n <extended-address nil=\"true\"></extended-address>\n <locality nil=\"true\"></locality>\n <region nil=\"true\"></region>\n <postal-code nil=\"true\"></postal-code>\n <country-name nil=\"true\"></country-name>\n <country-code-alpha2 nil=\"true\"></country-code-alpha2>\n <country-code-alpha3 nil=\"true\"></country-code-alpha3>\n <country-code-numeric nil=\"true\"></country-code-numeric>\n </billing>\n <refund-id nil=\"true\"></refund-id>\n <refund-ids type=\"array\"/>\n <refunded-transaction-id nil=\"true\"></refunded-transaction-id>\n <settlement-batch-id nil=\"true\"></settlement-batch-id>\n <shipping>\n <id nil=\"true\"></id>\n <first-name nil=\"true\"></first-name>\n <last-name nil=\"true\"></last-name>\n <company nil=\"true\"></company>\n <street-address nil=\"true\"></street-address>\n <extended-address nil=\"true\"></extended-address>\n <locality nil=\"true\"></locality>\n <region nil=\"true\"></region>\n <postal-code nil=\"true\"></postal-code>\n <country-name nil=\"true\"></country-name>\n <country-code-alpha2 nil=\"true\"></country-code-alpha2>\n <country-code-alpha3 nil=\"true\"></country-code-alpha3>\n <country-code-numeric nil=\"true\"></country-code-numeric>\n </shipping>\n <custom-fields>\n </custom-fields>\n <avs-error-response-code nil=\"true\"></avs-error-response-code>\n <avs-postal-code-response-code>I</avs-postal-code-response-code>\n <avs-street-address-response-code>I</avs-street-address-response-code>\n <cvv-response-code>I</cvv-response-code>\n <gateway-rejection-reason nil=\"true\"></gateway-rejection-reason>\n <processor-authorization-code>ZKB4VJ</processor-authorization-code>\n <processor-response-code>1000</processor-response-code>\n <processor-response-text>Approved</processor-response-text>\n <credit-card>\n <token>8yq7</token>\n <bin>411111</bin>\n <last-4>1111</last-4>\n <card-type>Visa</card-type>\n <expiration-month>02</expiration-month>\n <expiration-year>2013</expiration-year>\n <customer-location>US</customer-location>\n <cardholder-name>Chad Lee Pytel</cardholder-name>\n </credit-card>\n <status-history type=\"array\">\n <status-event>\n <timestamp type=\"datetime\">2011-01-11T14:08:12Z</timestamp>\n <status>authorized</status>\n <amount>50.00</amount>\n <user>copycopter</user>\n <transaction-source>CP</transaction-source>\n </status-event>\n <status-event>\n <timestamp type=\"datetime\">2011-01-11T14:08:12Z</timestamp>\n <status>#{FakeBraintree.transaction[:status]}</status>\n <amount>50.00</amount>\n <user>copycopter</user>\n <transaction-source>CP</transaction-source>\n </status-event>\n </status-history>\n <subscription-id>#{FakeBraintree.transaction[:subscription_id]}</subscription-id>\n <add-ons type=\"array\"/>\n <discounts type=\"array\"/>\n </transaction>\n</credit-card-transactions>\n")]
116
+ [200, { "Content-Encoding" => "gzip" }, ActiveSupport::Gzip.compress(FakeBraintree.generated_transaction.to_xml)]
113
117
  end
114
118
  end
@@ -296,4 +296,44 @@ describe Account, "with a paid subscription" do
296
296
  subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
297
297
  end
298
298
  end
299
+
300
+ it "receives a receipt email at it's billing email with transaction details" do
301
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
302
+ subscription["status"] = Braintree::Subscription::Status::Active
303
+ subscription["next_billing_date"] = 2.months.from_now
304
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
305
+ :subscription_id => subject.subscription_token }
306
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
307
+
308
+ Timecop.travel(subject.next_billing_date + 1.day) do
309
+ ActionMailer::Base.deliveries.clear
310
+
311
+ Account.update_subscriptions!
312
+
313
+ ActionMailer::Base.deliveries.any? do |email|
314
+ email.to == [subject.billing_email] &&
315
+ email.subject =~ /receipt/i
316
+ end.should be
317
+ end
318
+ end
319
+
320
+ it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
321
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
322
+ subscription["status"] = Braintree::Subscription::Status::PastDue
323
+ subscription["next_billing_date"] = 2.months.from_now
324
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
325
+ :subscription_id => subject.subscription_token }
326
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
327
+
328
+ Timecop.travel(subject.next_billing_date + 1.day) do
329
+ ActionMailer::Base.deliveries.clear
330
+
331
+ Account.update_subscriptions!
332
+
333
+ ActionMailer::Base.deliveries.any? do |email|
334
+ email.to == [subject.billing_email] &&
335
+ email.subject =~ /problem/i
336
+ end.should be
337
+ end
338
+ end
299
339
  end
@@ -4,4 +4,8 @@ describe Saucy::Configuration do
4
4
  it "has layouts" do
5
5
  subject.layouts.should be_a(Saucy::Layouts)
6
6
  end
7
+
8
+ it "has a mailer_sender" do
9
+ subject.mailer_sender.should_not be_nil
10
+ end
7
11
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saucy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 3
10
- version: 0.2.3
9
+ - 4
10
+ version: 0.2.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - thoughtbot, inc.
@@ -128,6 +128,7 @@ files:
128
128
  - Gemfile.lock
129
129
  - Rakefile
130
130
  - README.md
131
+ - config/locales/en.yml
131
132
  - config/routes.rb
132
133
  - app/controllers/accounts_controller.rb
133
134
  - app/controllers/billings_controller.rb
@@ -135,6 +136,7 @@ files:
135
136
  - app/controllers/memberships_controller.rb
136
137
  - app/controllers/plans_controller.rb
137
138
  - app/controllers/profiles_controller.rb
139
+ - app/mailers/billing_mailer.rb
138
140
  - app/models/invitation.rb
139
141
  - app/models/limit.rb
140
142
  - app/models/membership.rb
@@ -147,9 +149,12 @@ files:
147
149
  - app/views/accounts/edit.html.erb
148
150
  - app/views/accounts/index.html.erb
149
151
  - app/views/accounts/new.html.erb
152
+ - app/views/billing_mailer/problem.html.erb
153
+ - app/views/billing_mailer/problem.text.erb
154
+ - app/views/billing_mailer/receipt.html.erb
155
+ - app/views/billing_mailer/receipt.text.erb
150
156
  - app/views/billings/_form.html.erb
151
157
  - app/views/billings/edit.html.erb
152
- - app/views/billings/past_due.html.erb
153
158
  - app/views/billings/show.html.erb
154
159
  - app/views/invitation_mailer/invitation.text.erb
155
160
  - app/views/invitations/new.html.erb
File without changes