payola-payments 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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