payola-payments 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/payola/checkout_button.js +8 -1
  3. data/app/assets/javascripts/payola/subscription_form_onestep.js +13 -2
  4. data/app/assets/javascripts/payola/subscription_form_twostep.js +10 -1
  5. data/app/controllers/concerns/payola/async_behavior.rb +14 -5
  6. data/app/controllers/payola/subscriptions_controller.rb +16 -3
  7. data/app/controllers/payola/transactions_controller.rb +1 -1
  8. data/app/models/concerns/payola/plan.rb +1 -1
  9. data/app/models/payola/subscription.rb +7 -1
  10. data/app/services/payola/change_subscription_quantity.rb +25 -0
  11. data/app/services/payola/charge_card.rb +8 -4
  12. data/app/services/payola/create_subscription.rb +1 -0
  13. data/app/services/payola/start_subscription.rb +48 -12
  14. data/app/views/payola/subscriptions/_change_plan.html.erb +1 -1
  15. data/config/database.yml.ci +6 -0
  16. data/config/routes.rb +1 -0
  17. data/db/migrate/20141213205847_add_active_to_payola_coupon.rb +5 -0
  18. data/lib/payola.rb +4 -4
  19. data/lib/payola/engine.rb +1 -1
  20. data/lib/payola/version.rb +1 -1
  21. data/lib/payola/worker.rb +1 -1
  22. data/spec/concerns/plan_spec.rb +9 -0
  23. data/spec/concerns/sellable_spec.rb +1 -1
  24. data/spec/controllers/payola/subscriptions_controller_spec.rb +41 -13
  25. data/spec/controllers/payola/transactions_controller_spec.rb +7 -5
  26. data/spec/dummy/app/models/user.rb +2 -0
  27. data/spec/dummy/app/views/subscribe/index.html.erb +2 -0
  28. data/spec/dummy/config/environments/test.rb +5 -1
  29. data/spec/dummy/db/development.sqlite3 +0 -0
  30. data/spec/dummy/db/migrate/20141204170622_create_users.rb +8 -0
  31. data/spec/dummy/db/schema.rb +16 -10
  32. data/spec/dummy/db/test.sqlite3 +0 -0
  33. data/spec/dummy/log/development.log +131 -0
  34. data/spec/dummy/log/test.log +75119 -0
  35. data/spec/factories/subscription_plan.rb +2 -2
  36. data/spec/mailers/payola/receipt_mailer_spec.rb +6 -1
  37. data/spec/models/payola/coupon_spec.rb +27 -0
  38. data/spec/payola_spec.rb +2 -2
  39. data/spec/services/payola/change_subscription_plan_spec.rb +0 -1
  40. data/spec/services/payola/change_subscription_quantity_spec.rb +29 -0
  41. data/spec/services/payola/charge_card_spec.rb +9 -0
  42. data/spec/services/payola/start_subscription_spec.rb +37 -0
  43. data/spec/worker_spec.rb +1 -1
  44. metadata +15 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b22da0af6dd975621942e21d8cbb0c53d9313b7
4
- data.tar.gz: 0f417cc9ddcdc23fc2cb9b3a7a930489269ba320
3
+ metadata.gz: 247fe7a59796463c2fe0ed750a5ca659eb76423b
4
+ data.tar.gz: 5c42d5a2a48234a91d33e0f6bf9c250fc1cc412b
5
5
  SHA512:
6
- metadata.gz: f3cca9d9ff83f689ef48d576c8c13469f0770c3f4b8da64b99a0a7751c688bb7add4de4bf042678bf4a8b88d44e66a9c933dfdb0586af49247dcc9261be34fac
7
- data.tar.gz: e18f346ba5cb2a0ba01399535b9ad32f413c5afce3ea93a75754b57c42627449fb83b9df3db9c32722c113eeec524fb75bd2c3b76a53d48a05f60e651e1f28f9
6
+ metadata.gz: 68fbf08cf3771d397a60ce0918cd7ab0b2db98ac981b32fa3d80140651ec558c65eb40f42cff32a37ff88f444d10acad8b207c1179e06624cb06a9bcae40d5d6
7
+ data.tar.gz: 5343de6dd80fb7357678a70fcac9f30bc0806903e1a83f08a6ec7dc66aa48463356f87c964caa19b11e0da1c1f9e5ec14cbd482fc764ae8ba79d5b76efd41b20
@@ -65,7 +65,7 @@ var PayolaCheckout = {
65
65
  return;
66
66
  }
67
67
 
68
- $.get(options.base_path + "/status/" + guid, function(data) {
68
+ var handler = function(data) {
69
69
  if (data.status === "finished") {
70
70
  window.location = options.base_path + "/confirm/" + guid;
71
71
  } else if (data.status === "errored") {
@@ -73,6 +73,13 @@ var PayolaCheckout = {
73
73
  } else {
74
74
  setTimeout(function() { PayolaCheckout.poll(guid, num_retries_left - 1, options); }, 500);
75
75
  }
76
+ };
77
+
78
+ $.ajax({
79
+ type: "GET",
80
+ url: options.base_path + "/status/" + guid,
81
+ success: handler,
82
+ error: handler
76
83
  });
77
84
  }
78
85
  };
@@ -20,20 +20,24 @@ var PayolaOnestepSubscriptionForm = {
20
20
  } else {
21
21
  var email = form.find("[data-payola='email']").val();
22
22
  var coupon = form.find("[data-payola='coupon']").val();
23
+ var quantity = form.find("[data-payola='quantity']").val();
23
24
 
24
25
  var base_path = form.data('payola-base-path');
25
26
  var plan_type = form.data('payola-plan-type');
26
27
  var plan_id = form.data('payola-plan-id');
27
28
 
29
+ var action = $(form).attr('action');
30
+
28
31
  form.append($('<input type="hidden" name="plan_type">').val(plan_type));
29
32
  form.append($('<input type="hidden" name="plan_id">').val(plan_id));
30
33
  form.append($('<input type="hidden" name="stripeToken">').val(response.id));
31
34
  form.append($('<input type="hidden" name="stripeEmail">').val(email));
32
35
  form.append($('<input type="hidden" name="coupon">').val(coupon));
36
+ form.append($('<input type="hidden" name="quantity">').val(quantity));
33
37
  form.append(PayolaOnestepSubscriptionForm.authenticityTokenInput());
34
38
  $.ajax({
35
39
  type: "POST",
36
- url: form.action,
40
+ url: action,
37
41
  data: form.serialize(),
38
42
  success: function(data) { PayolaOnestepSubscriptionForm.poll(form, 60, data.guid, base_path); },
39
43
  error: function(data) { PayolaOnestepSubscriptionForm.showError(form, data.responseJSON.error); }
@@ -45,7 +49,7 @@ var PayolaOnestepSubscriptionForm = {
45
49
  if (num_retries_left === 0) {
46
50
  PayolaOnestepSubscriptionForm.showError(form, "This seems to be taking too long. Please contact support and give them transaction ID: " + guid);
47
51
  }
48
- $.get(base_path + '/subscription_status/' + guid, function(data) {
52
+ var handler = function(data) {
49
53
  if (data.status === "active") {
50
54
  window.location = base_path + '/confirm_subscription/' + guid;
51
55
  } else if (data.status === "errored") {
@@ -53,6 +57,13 @@ var PayolaOnestepSubscriptionForm = {
53
57
  } else {
54
58
  setTimeout(function() { PayolaOnestepSubscriptionForm.poll(form, num_retries_left - 1, guid, base_path); }, 500);
55
59
  }
60
+ };
61
+
62
+ $.ajax({
63
+ type: 'GET',
64
+ url: base_path + '/subscription_status/' + guid,
65
+ success: handler,
66
+ error: handler
56
67
  });
57
68
  },
58
69
 
@@ -20,6 +20,7 @@ var PayolaSubscriptionForm = {
20
20
  } else {
21
21
  var email = form.find("[data-payola='email']").val();
22
22
  var coupon = form.find("[data-payola='coupon']").val();
23
+ var quantity = form.find("[data-payola='quantity']").val();
23
24
 
24
25
  var base_path = form.data('payola-base-path');
25
26
  var plan_type = form.data('payola-plan-type');
@@ -29,6 +30,7 @@ var PayolaSubscriptionForm = {
29
30
  data_form.append($('<input type="hidden" name="stripeToken">').val(response.id));
30
31
  data_form.append($('<input type="hidden" name="stripeEmail">').val(email));
31
32
  data_form.append($('<input type="hidden" name="coupon">').val(coupon));
33
+ data_form.append($('<input type="hidden" name="quantity">').val(quantity));
32
34
  data_form.append(PayolaSubscriptionForm.authenticityTokenInput());
33
35
  $.ajax({
34
36
  type: "POST",
@@ -44,7 +46,7 @@ var PayolaSubscriptionForm = {
44
46
  if (num_retries_left === 0) {
45
47
  PayolaSubscriptionForm.showError(form, "This seems to be taking too long. Please contact support and give them transaction ID: " + guid);
46
48
  }
47
- $.get(base_path + '/subscription_status/' + guid, function(data) {
49
+ var handler = function(data) {
48
50
  if (data.status === "active") {
49
51
  form.append($('<input type="hidden" name="payola_subscription_guid"></input>').val(guid));
50
52
  form.append(PayolaSubscriptionForm.authenticityTokenInput());
@@ -54,6 +56,13 @@ var PayolaSubscriptionForm = {
54
56
  } else {
55
57
  setTimeout(function() { PayolaSubscriptionForm.poll(form, num_retries_left - 1, guid, base_path); }, 500);
56
58
  }
59
+ };
60
+
61
+ $.ajax({
62
+ type: 'GET',
63
+ url: base_path + '/subscription_status/' + guid,
64
+ success: handler,
65
+ error: handler
57
66
  });
58
67
  },
59
68
 
@@ -16,11 +16,20 @@ module Payola
16
16
  end
17
17
 
18
18
  def create_object(object_class, object_creator_class, object_processor_class, product_key, product)
19
- create_params = params.permit!.merge(
20
- product_key => product,
21
- coupon: @coupon,
22
- affiliate: @affiliate
23
- )
19
+ create_params = if object_class == Subscription
20
+ params.permit!.merge(
21
+ product_key => product,
22
+ coupon: @coupon,
23
+ quantity: @quantity,
24
+ affiliate: @affiliate
25
+ )
26
+ else
27
+ params.permit!.merge(
28
+ product_key => product,
29
+ coupon: @coupon,
30
+ affiliate: @affiliate
31
+ )
32
+ end
24
33
 
25
34
  object = object_creator_class.call(create_params)
26
35
 
@@ -4,8 +4,8 @@ module Payola
4
4
  include Payola::StatusBehavior
5
5
  include Payola::AsyncBehavior
6
6
 
7
- before_filter :find_plan_and_coupon, only: [:create, :change_plan]
8
- before_filter :check_modify_permissions, only: [:destroy, :change_plan, :update_card]
7
+ before_filter :find_plan_coupon_and_quantity, only: [:create, :change_plan]
8
+ before_filter :check_modify_permissions, only: [:destroy, :change_plan, :change_quantity, :update_card]
9
9
 
10
10
  def show
11
11
  show_object(Subscription)
@@ -32,6 +32,14 @@ module Payola
32
32
  confirm_with_message("Subscription plan updated")
33
33
  end
34
34
 
35
+ def change_quantity
36
+ find_quantity
37
+ @subscription = Subscription.find_by!(guid: params[:guid])
38
+ Payola::ChangeSubscriptionQuantity.call(@subscription, @quantity)
39
+
40
+ confirm_with_message("Subscription quantity updated")
41
+ end
42
+
35
43
  def update_card
36
44
  @subscription = Subscription.find_by!(guid: params[:guid])
37
45
  Payola::UpdateCard.call(@subscription, params[:stripeToken])
@@ -41,9 +49,10 @@ module Payola
41
49
 
42
50
  private
43
51
 
44
- def find_plan_and_coupon
52
+ def find_plan_coupon_and_quantity
45
53
  find_plan
46
54
  find_coupon
55
+ find_quantity
47
56
  end
48
57
 
49
58
  def find_plan
@@ -58,6 +67,10 @@ module Payola
58
67
  @coupon = cookies[:cc] || params[:cc] || params[:coupon_code] || params[:coupon]
59
68
  end
60
69
 
70
+ def find_quantity
71
+ @quantity = params[:quantity].blank? ? 1 : params[:quantity].to_i
72
+ end
73
+
61
74
  def check_modify_permissions
62
75
  subscription = Subscription.find_by!(guid: params[:guid])
63
76
  if self.respond_to?(:payola_can_modify_subscription?)
@@ -35,7 +35,7 @@ module Payola
35
35
  def find_coupon
36
36
  coupon_code = cookies[:cc] || params[:cc] || params[:coupon_code]
37
37
  @coupon = Coupon.where('lower(code) = lower(?)', coupon_code).first
38
- if @coupon
38
+ if @coupon && @coupon.active?
39
39
  cookies[:cc] = coupon_code
40
40
  @price = @product.price * (1 - @coupon.percent_off / 100.0)
41
41
  else
@@ -12,7 +12,7 @@ module Payola
12
12
 
13
13
  validates_uniqueness_of :stripe_id
14
14
 
15
- before_save :create_stripe_plan, on: :create
15
+ before_create :create_stripe_plan
16
16
 
17
17
  has_many :subscriptions, :class_name => "Payola::Subscription"
18
18
 
@@ -20,7 +20,7 @@ module Payola
20
20
 
21
21
  include AASM
22
22
 
23
- attr_accessor :old_plan
23
+ attr_accessor :old_plan, :old_quantity
24
24
 
25
25
  aasm column: 'state', skip_validation_on_save: true do
26
26
  state :pending, initial: true
@@ -116,6 +116,12 @@ module Payola
116
116
  Payola.instrument(instrument_key('plan_changed', false), self)
117
117
  end
118
118
 
119
+ def instrument_quantity_changed(old_quantity)
120
+ self.old_quantity = old_quantity
121
+ Payola.instrument(instrument_key('quantity_changed'), self)
122
+ Payola.instrument(instrument_key('quantity_changed', false), self)
123
+ end
124
+
119
125
  def redirector
120
126
  plan
121
127
  end
@@ -0,0 +1,25 @@
1
+ module Payola
2
+ class ChangeSubscriptionQuantity
3
+ def self.call(subscription, quantity)
4
+ secret_key = Payola.secret_key_for_sale(subscription)
5
+ old_quantity = subscription.quantity
6
+
7
+ begin
8
+ customer = Stripe::Customer.retrieve(subscription.stripe_customer_id, secret_key)
9
+ sub = customer.subscriptions.retrieve(subscription.stripe_id)
10
+ sub.quantity = quantity
11
+ sub.save
12
+
13
+ subscription.quantity = quantity
14
+ subscription.save!
15
+
16
+ subscription.instrument_plan_changed(old_quantity)
17
+
18
+ rescue RuntimeError, Stripe::StripeError => e
19
+ subscription.errors[:base] << e.message
20
+ end
21
+
22
+ subscription
23
+ end
24
+ end
25
+ end
@@ -22,10 +22,14 @@ module Payola
22
22
  end
23
23
 
24
24
  def self.create_customer(sale, secret_key)
25
- Stripe::Customer.create({
26
- card: sale.stripe_token,
27
- email: sale.email
28
- }, secret_key)
25
+ if sale.stripe_customer_id.present?
26
+ Stripe::Customer.retrieve(sale.stripe_customer_id, secret_key)
27
+ else
28
+ Stripe::Customer.create({
29
+ card: sale.stripe_token,
30
+ email: sale.email
31
+ }, secret_key)
32
+ end
29
33
  end
30
34
 
31
35
  def self.create_charge(sale, customer, secret_key)
@@ -13,6 +13,7 @@ module Payola
13
13
  s.coupon = params[:coupon]
14
14
  s.signed_custom_fields = params[:signed_custom_fields]
15
15
  s.setup_fee = params[:setup_fee]
16
+ s.quantity = params[:quantity]
16
17
 
17
18
  s.owner = owner
18
19
  s.amount = plan.amount
@@ -1,35 +1,42 @@
1
1
  module Payola
2
2
  class StartSubscription
3
+ attr_reader :subscription, :secret_key
4
+
3
5
  def self.call(subscription)
4
6
  subscription.save!
5
7
  secret_key = Payola.secret_key_for_sale(subscription)
6
8
 
9
+ new(subscription, secret_key).run
10
+ end
11
+
12
+ def initialize(subscription, secret_key)
13
+ @subscription = subscription
14
+ @secret_key = secret_key
15
+ end
16
+
17
+ def run
7
18
  begin
8
19
  subscription.verify_charge!
9
20
 
21
+ customer = find_or_create_customer
22
+
10
23
  create_params = {
11
- card: subscription.stripe_token,
12
- email: subscription.email,
13
- plan: subscription.plan.stripe_id,
24
+ plan: subscription.plan.stripe_id,
25
+ quantity: subscription.quantity
14
26
  }
15
27
  create_params[:coupon] = subscription.coupon if subscription.coupon.present?
16
- create_params[:account_balance] = subscription.setup_fee if subscription.setup_fee.present?
17
-
18
- customer = Stripe::Customer.create(create_params, secret_key)
28
+ stripe_sub = customer.subscriptions.create(create_params)
19
29
 
20
30
  card = customer.cards.data.first
21
31
  subscription.update_attributes(
22
- stripe_id: customer.subscriptions.data.first.id,
32
+ stripe_id: stripe_sub.id,
23
33
  stripe_customer_id: customer.id,
24
34
  card_last4: card.last4,
25
35
  card_expiration: Date.new(card.exp_year, card.exp_month, 1),
26
36
  card_type: card.respond_to?(:brand) ? card.brand : card.type
27
37
  )
28
38
  subscription.activate!
29
- rescue Stripe::StripeError => e
30
- subscription.update_attributes(error: e.message)
31
- subscription.fail!
32
- rescue RuntimeError => e
39
+ rescue Stripe::StripeError, RuntimeError => e
33
40
  subscription.update_attributes(error: e.message)
34
41
  subscription.fail!
35
42
  end
@@ -37,5 +44,34 @@ module Payola
37
44
  subscription
38
45
  end
39
46
 
47
+ def find_or_create_customer
48
+ subs = Subscription.where(owner: subscription.owner) if subscription.owner
49
+ if subs && subs.length > 1
50
+ first_sub = subs.first
51
+ customer_id = first_sub.stripe_customer_id
52
+ return Stripe::Customer.retrieve(customer_id, secret_key)
53
+ else
54
+ customer_create_params = {
55
+ card: subscription.stripe_token,
56
+ email: subscription.email
57
+ }
58
+
59
+ customer = Stripe::Customer.create(customer_create_params, secret_key)
60
+ end
61
+
62
+ if subscription.setup_fee.present?
63
+ plan = subscription.plan
64
+ description = plan.try(:setup_fee_description, subscription) || 'Setup Fee'
65
+ Stripe::InvoiceItem.create(
66
+ customer: customer.id,
67
+ amount: subscription.setup_fee,
68
+ currency: subscription.currency,
69
+ description: description
70
+ )
71
+ end
72
+
73
+ customer
74
+ end
40
75
  end
41
- end
76
+
77
+ end
@@ -3,7 +3,7 @@
3
3
  button_text = local_assigns.fetch :button_text, "Update Plan"
4
4
  %>
5
5
 
6
- <%= form_for subscription, method: :post, url: change_subscription_plan(subscription) do |f| %>
6
+ <%= form_for subscription, method: :post, url: payola.change_subscription_plan_path(subscription) do |f| %>
7
7
  <%= hidden_field_tag 'plan_class', new_plan.plan_class %>
8
8
  <%= hidden_field_tag 'plan_id', new_plan.id %>
9
9
  <%= button_tag button_text, class: button_class %>
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: postgresql
3
+ database: app_test
4
+ pool: 5
5
+ username:
6
+ password:
data/config/routes.rb CHANGED
@@ -8,6 +8,7 @@ Payola::Engine.routes.draw do
8
8
  match '/subscription_status/:guid' => 'subscriptions#status', via: :get, as: :subscription_status
9
9
  match '/cancel_subscription/:guid' => 'subscriptions#destroy', via: :delete, as: :cancel_subscription
10
10
  match '/change_plan/:guid' => 'subscriptions#change_plan', via: :post, as: :change_subscription_plan
11
+ match '/change_quantity/:guid' => 'subscriptions#change_quantity', via: :post, as: :change_subscription_quantity
11
12
  match '/update_card/:guid' => 'subscriptions#update_card', via: :post, as: :update_card
12
13
 
13
14
  mount StripeEvent::Engine => '/events'
@@ -0,0 +1,5 @@
1
+ class AddActiveToPayolaCoupon < ActiveRecord::Migration
2
+ def change
3
+ add_column :payola_coupons, :active, :boolean, default: true
4
+ end
5
+ end
data/lib/payola.rb CHANGED
@@ -75,10 +75,10 @@ module Payola
75
75
  self.background_worker = nil
76
76
  self.event_filter = lambda { |event| event }
77
77
  self.charge_verifier = lambda { |event| true }
78
- self.publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
79
- self.secret_key = ENV['STRIPE_SECRET_KEY']
80
- self.secret_key_retriever = lambda { |sale| Payola.secret_key }
81
- self.publishable_key_retriever = lambda { |sale| Payola.publishable_key }
78
+ self.publishable_key = lambda { ENV['STRIPE_PUBLISHABLE_KEY'] }
79
+ self.secret_key = lambda { ENV['STRIPE_SECRET_KEY'] }
80
+ self.secret_key_retriever = lambda { |sale| Payola.secret_key.respond_to?(:call) ? Payola.secret_key.call() : Payola.secret_key }
81
+ self.publishable_key_retriever = lambda { |sale| Payola.publishable_key.respond_to?(:call) ? Payola.publishable_key.call() : Payola.publishable_key }
82
82
  self.support_email = 'sales@example.com'
83
83
  self.default_currency = 'usd'
84
84
  self.sellables = {}
data/lib/payola/engine.rb CHANGED
@@ -20,7 +20,7 @@ module Payola
20
20
 
21
21
  initializer :inject_helpers do |app|
22
22
  ActiveSupport.on_load :action_controller do
23
- ::ApplicationController.send(:helper, Payola::PriceHelper)
23
+ ::ActionController::Base.send(:helper, Payola::PriceHelper)
24
24
  end
25
25
 
26
26
  ActiveSupport.on_load :action_mailer do