payola-payments 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/payola/checkout_button.js +9 -9
- data/app/assets/javascripts/payola/form.js +6 -6
- data/app/assets/javascripts/payola/subscription_form.js +6 -6
- data/app/controllers/concerns/payola/affiliate_behavior.rb +17 -0
- data/app/controllers/concerns/payola/async_behavior.rb +36 -0
- data/app/controllers/payola/subscriptions_controller.rb +31 -46
- data/app/controllers/payola/transactions_controller.rb +16 -32
- data/app/mailers/payola/admin_mailer.rb +7 -15
- data/app/mailers/payola/receipt_mailer.rb +2 -0
- data/app/models/concerns/payola/guid_behavior.rb +20 -0
- data/app/models/concerns/payola/plan.rb +0 -1
- data/app/models/payola/sale.rb +6 -12
- data/app/models/payola/subscription.rb +9 -12
- data/app/services/payola/charge_card.rb +42 -33
- data/app/services/payola/create_plan.rb +2 -2
- data/app/services/payola/invoice_behavior.rb +55 -0
- data/app/services/payola/invoice_failed.rb +4 -29
- data/app/services/payola/invoice_paid.rb +4 -31
- data/lib/payola.rb +13 -11
- data/lib/payola/version.rb +1 -1
- data/spec/concerns/plan_spec.rb +0 -5
- data/spec/controllers/payola/subscriptions_controller_spec.rb +10 -1
- data/spec/controllers/payola/transactions_controller_spec.rb +10 -1
- data/spec/dummy/app/models/subscription_plan_without_interval_count.rb +3 -0
- data/spec/dummy/config/environments/test.rb +2 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20141120170744_create_subscription_plan_without_interval_counts.rb +12 -0
- data/spec/dummy/db/schema.rb +10 -1
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +222 -0
- data/spec/dummy/log/test.log +15830 -141203
- data/spec/factories/subscription_plan.rb +7 -0
- data/spec/mailers/payola/admin_mailer_spec.rb +35 -0
- data/spec/mailers/payola/receipt_mailer_spec.rb +52 -0
- data/spec/models/payola/subscription_spec.rb +21 -0
- data/spec/payola_spec.rb +6 -2
- data/spec/services/payola/create_plan_spec.rb +9 -0
- data/spec/services/payola/process_sale_spec.rb +15 -0
- data/spec/services/payola/process_subscription_spec.rb +15 -0
- data/spec/services/payola/send_mail_spec.rb +25 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/worker_spec.rb +55 -0
- metadata +40 -8
data/app/models/payola/sale.rb
CHANGED
@@ -2,6 +2,8 @@ require 'aasm'
|
|
2
2
|
|
3
3
|
module Payola
|
4
4
|
class Sale < ActiveRecord::Base
|
5
|
+
include Payola::GuidBehavior
|
6
|
+
|
5
7
|
has_paper_trail if respond_to? :has_paper_trail
|
6
8
|
|
7
9
|
validates_presence_of :email
|
@@ -10,10 +12,6 @@ module Payola
|
|
10
12
|
validates_presence_of :stripe_token
|
11
13
|
validates_presence_of :currency
|
12
14
|
|
13
|
-
validates_uniqueness_of :guid
|
14
|
-
|
15
|
-
before_save :populate_guid
|
16
|
-
|
17
15
|
belongs_to :product, polymorphic: true
|
18
16
|
belongs_to :owner, polymorphic: true
|
19
17
|
belongs_to :coupon
|
@@ -74,6 +72,10 @@ module Payola
|
|
74
72
|
end
|
75
73
|
end
|
76
74
|
|
75
|
+
def redirector
|
76
|
+
product
|
77
|
+
end
|
78
|
+
|
77
79
|
private
|
78
80
|
|
79
81
|
def charge_card
|
@@ -107,13 +109,5 @@ module Payola
|
|
107
109
|
end
|
108
110
|
end
|
109
111
|
|
110
|
-
def populate_guid
|
111
|
-
if new_record?
|
112
|
-
while !valid? || self.guid.nil?
|
113
|
-
self.guid = SecureRandom.random_number(1_000_000_000).to_s(32)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
112
|
end
|
119
113
|
end
|
@@ -2,6 +2,9 @@ require 'aasm'
|
|
2
2
|
|
3
3
|
module Payola
|
4
4
|
class Subscription < ActiveRecord::Base
|
5
|
+
include Payola::GuidBehavior
|
6
|
+
|
7
|
+
has_paper_trail if respond_to? :has_paper_trail
|
5
8
|
|
6
9
|
validates_presence_of :email
|
7
10
|
validates_presence_of :plan_id
|
@@ -9,10 +12,6 @@ module Payola
|
|
9
12
|
validates_presence_of :stripe_token
|
10
13
|
validates_presence_of :currency
|
11
14
|
|
12
|
-
validates_uniqueness_of :guid
|
13
|
-
|
14
|
-
before_save :populate_guid
|
15
|
-
|
16
15
|
belongs_to :plan, polymorphic: true
|
17
16
|
belongs_to :owner, polymorphic: true
|
18
17
|
belongs_to :affiliate
|
@@ -98,8 +97,10 @@ module Payola
|
|
98
97
|
self.ended_at = Time.at(stripe_sub.ended_at) if stripe_sub.ended_at
|
99
98
|
self.trial_start = Time.at(stripe_sub.trial_start) if stripe_sub.trial_start
|
100
99
|
self.trial_end = Time.at(stripe_sub.trial_end) if stripe_sub.trial_end
|
100
|
+
self.canceled_at = Time.at(stripe_sub.canceled_at) if stripe_sub.canceled_at
|
101
101
|
self.quantity = stripe_sub.quantity
|
102
102
|
self.stripe_status = stripe_sub.status
|
103
|
+
self.cancel_at_period_end = stripe_sub.cancel_at_period_end
|
103
104
|
|
104
105
|
self.save!
|
105
106
|
self
|
@@ -115,6 +116,10 @@ module Payola
|
|
115
116
|
Payola.instrument(instrument_key('plan_changed', false), self)
|
116
117
|
end
|
117
118
|
|
119
|
+
def redirector
|
120
|
+
plan
|
121
|
+
end
|
122
|
+
|
118
123
|
private
|
119
124
|
|
120
125
|
def start_subscription
|
@@ -149,13 +154,5 @@ module Payola
|
|
149
154
|
end
|
150
155
|
end
|
151
156
|
|
152
|
-
def populate_guid
|
153
|
-
if new_record?
|
154
|
-
while !valid? || self.guid.nil?
|
155
|
-
self.guid = SecureRandom.random_number(1_000_000_000).to_s(32)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
157
|
end
|
161
158
|
end
|
@@ -7,40 +7,13 @@ module Payola
|
|
7
7
|
begin
|
8
8
|
sale.verify_charge!
|
9
9
|
|
10
|
-
customer =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
charge_attributes = {
|
16
|
-
amount: sale.amount,
|
17
|
-
currency: sale.currency,
|
18
|
-
customer: customer.id,
|
19
|
-
description: sale.guid,
|
20
|
-
}.merge(Payola.additional_charge_attributes.call(sale, customer))
|
21
|
-
|
22
|
-
charge = Stripe::Charge.create(charge_attributes, secret_key)
|
23
|
-
|
24
|
-
if charge.respond_to?(:fee)
|
25
|
-
fee = charge.fee
|
26
|
-
else
|
27
|
-
balance = Stripe::BalanceTransaction.retrieve(charge.balance_transaction, secret_key)
|
28
|
-
fee = balance.fee
|
29
|
-
end
|
30
|
-
|
31
|
-
sale.update_attributes(
|
32
|
-
stripe_id: charge.id,
|
33
|
-
stripe_customer_id: customer.id,
|
34
|
-
card_last4: charge.card.last4,
|
35
|
-
card_expiration: Date.new(charge.card.exp_year, charge.card.exp_month, 1),
|
36
|
-
card_type: charge.card.respond_to?(:brand) ? charge.card.brand : charge.card.type,
|
37
|
-
fee_amount: fee
|
38
|
-
)
|
10
|
+
customer = create_customer(sale, secret_key)
|
11
|
+
charge = create_charge(sale, customer, secret_key)
|
12
|
+
|
13
|
+
update_sale(sale, customer, charge, secret_key)
|
14
|
+
|
39
15
|
sale.finish!
|
40
|
-
rescue Stripe::StripeError => e
|
41
|
-
sale.update_attributes(error: e.message)
|
42
|
-
sale.fail!
|
43
|
-
rescue RuntimeError => e
|
16
|
+
rescue Stripe::StripeError, RuntimeError => e
|
44
17
|
sale.update_attributes(error: e.message)
|
45
18
|
sale.fail!
|
46
19
|
end
|
@@ -48,5 +21,41 @@ module Payola
|
|
48
21
|
sale
|
49
22
|
end
|
50
23
|
|
24
|
+
def self.create_customer(sale, secret_key)
|
25
|
+
Stripe::Customer.create({
|
26
|
+
card: sale.stripe_token,
|
27
|
+
email: sale.email
|
28
|
+
}, secret_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create_charge(sale, customer, secret_key)
|
32
|
+
charge_attributes = {
|
33
|
+
amount: sale.amount,
|
34
|
+
currency: sale.currency,
|
35
|
+
customer: customer.id,
|
36
|
+
description: sale.guid,
|
37
|
+
}.merge(Payola.additional_charge_attributes.call(sale, customer))
|
38
|
+
|
39
|
+
Stripe::Charge.create(charge_attributes, secret_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.update_sale(sale, customer, charge, secret_key)
|
43
|
+
if charge.respond_to?(:fee)
|
44
|
+
fee = charge.fee
|
45
|
+
else
|
46
|
+
balance = Stripe::BalanceTransaction.retrieve(charge.balance_transaction, secret_key)
|
47
|
+
fee = balance.fee
|
48
|
+
end
|
49
|
+
|
50
|
+
sale.update_attributes(
|
51
|
+
stripe_id: charge.id,
|
52
|
+
stripe_customer_id: customer.id,
|
53
|
+
card_last4: charge.card.last4,
|
54
|
+
card_expiration: Date.new(charge.card.exp_year, charge.card.exp_month, 1),
|
55
|
+
card_type: charge.card.respond_to?(:brand) ? charge.card.brand : charge.card.type,
|
56
|
+
fee_amount: fee
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
51
60
|
end
|
52
61
|
end
|
@@ -7,9 +7,9 @@ module Payola
|
|
7
7
|
id: plan.stripe_id,
|
8
8
|
amount: plan.amount,
|
9
9
|
interval: plan.interval,
|
10
|
-
interval_count: plan.interval_count,
|
11
|
-
currency: plan.respond_to?(:currency) ? plan.currency : Payola.default_currency,
|
12
10
|
name: plan.name,
|
11
|
+
interval_count: plan.respond_to?(:interval_count) ? plan.interval_count : nil,
|
12
|
+
currency: plan.respond_to?(:currency) ? plan.currency : Payola.default_currency,
|
13
13
|
trial_period_days: plan.respond_to?(:trial_period_days) ? plan.trial_period_days : nil
|
14
14
|
}, secret_key)
|
15
15
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Payola
|
4
|
+
module InvoiceBehavior
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def create_sale_from_event(event)
|
9
|
+
invoice = event.data.object
|
10
|
+
|
11
|
+
return unless invoice.charge
|
12
|
+
|
13
|
+
subscription = Payola::Subscription.find_by!(stripe_id: invoice.subscription)
|
14
|
+
secret_key = Payola.secret_key_for_sale(subscription)
|
15
|
+
|
16
|
+
stripe_sub = Stripe::Customer.retrieve(subscription.stripe_customer_id, secret_key).subscriptions.retrieve(invoice.subscription, secret_key)
|
17
|
+
subscription.sync_with!(stripe_sub)
|
18
|
+
|
19
|
+
sale = create_sale(subscription, invoice)
|
20
|
+
|
21
|
+
charge = Stripe::Charge.retrieve(invoice.charge, secret_key)
|
22
|
+
|
23
|
+
update_sale_with_charge(sale, charge)
|
24
|
+
|
25
|
+
return sale, charge
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_sale(subscription, invoice)
|
29
|
+
Payola::Sale.new do |s|
|
30
|
+
s.email = subscription.email
|
31
|
+
s.state = 'processing'
|
32
|
+
s.owner = subscription
|
33
|
+
s.product = subscription.plan
|
34
|
+
s.stripe_token = 'invoice'
|
35
|
+
s.amount = invoice.total
|
36
|
+
s.currency = invoice.currency
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_sale_with_charge(sale, charge)
|
41
|
+
sale.stripe_id = charge.id
|
42
|
+
sale.card_type = charge.card.respond_to?(:brand) ? charge.card.brand : charge.card.type
|
43
|
+
sale.card_last4 = charge.card.last4
|
44
|
+
|
45
|
+
if charge.respond_to?(:fee)
|
46
|
+
sale.fee_amount = charge.fee
|
47
|
+
else
|
48
|
+
balance = Stripe::BalanceTransaction.retrieve(charge.balance_transaction, secret_key)
|
49
|
+
sale.fee_amount = balance.fee
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -1,36 +1,11 @@
|
|
1
1
|
module Payola
|
2
2
|
class InvoiceFailed
|
3
|
-
|
4
|
-
invoice = event.data.object
|
5
|
-
|
6
|
-
subscription = Payola::Subscription.find_by!(stripe_id: invoice.subscription)
|
7
|
-
secret_key = Payola.secret_key_for_sale(subscription)
|
8
|
-
|
9
|
-
stripe_sub = Stripe::Customer.retrieve(subscription.stripe_customer_id, secret_key).subscriptions.retrieve(invoice.subscription, secret_key)
|
10
|
-
subscription.sync_with!(stripe_sub)
|
3
|
+
include Payola::InvoiceBehavior
|
11
4
|
|
12
|
-
|
13
|
-
|
14
|
-
s.state = 'processing'
|
15
|
-
s.owner = subscription
|
16
|
-
s.product = subscription.plan
|
17
|
-
s.stripe_token = 'invoice'
|
18
|
-
s.amount = invoice.total
|
19
|
-
s.currency = invoice.currency
|
20
|
-
end
|
21
|
-
|
22
|
-
charge = Stripe::Charge.retrieve(invoice.charge, secret_key)
|
23
|
-
|
24
|
-
sale.stripe_id = charge.id
|
25
|
-
sale.card_type = charge.card.respond_to?(:brand) ? charge.card.brand : charge.card.type
|
26
|
-
sale.card_last4 = charge.card.last4
|
5
|
+
def self.call(event)
|
6
|
+
sale, charge = create_sale_from_event(event)
|
27
7
|
|
28
|
-
|
29
|
-
sale.fee_amount = charge.fee
|
30
|
-
else
|
31
|
-
balance = Stripe::BalanceTransaction.retrieve(charge.balance_transaction, secret_key)
|
32
|
-
sale.fee_amount = balance.fee
|
33
|
-
end
|
8
|
+
return unless sale
|
34
9
|
|
35
10
|
sale.error = charge.failure_message
|
36
11
|
sale.save!
|
@@ -1,38 +1,11 @@
|
|
1
1
|
module Payola
|
2
2
|
class InvoicePaid
|
3
|
-
|
4
|
-
invoice = event.data.object
|
5
|
-
|
6
|
-
return unless invoice.charge
|
7
|
-
|
8
|
-
subscription = Payola::Subscription.find_by!(stripe_id: invoice.subscription)
|
9
|
-
|
10
|
-
secret_key = Payola.secret_key_for_sale(subscription)
|
11
|
-
stripe_sub = Stripe::Customer.retrieve(subscription.stripe_customer_id, secret_key).subscriptions.retrieve(invoice.subscription, secret_key)
|
12
|
-
subscription.sync_with!(stripe_sub)
|
3
|
+
include Payola::InvoiceBehavior
|
13
4
|
|
14
|
-
|
15
|
-
|
16
|
-
s.state = 'processing'
|
17
|
-
s.owner = subscription
|
18
|
-
s.product = subscription.plan
|
19
|
-
s.stripe_token = 'invoice'
|
20
|
-
s.amount = invoice.total
|
21
|
-
s.currency = invoice.currency
|
22
|
-
end
|
23
|
-
|
24
|
-
charge = Stripe::Charge.retrieve(invoice.charge, secret_key)
|
25
|
-
|
26
|
-
sale.stripe_id = charge.id
|
27
|
-
sale.card_type = charge.card.respond_to?(:brand) ? charge.card.brand : charge.card.type
|
28
|
-
sale.card_last4 = charge.card.last4
|
5
|
+
def self.call(event)
|
6
|
+
sale, charge = create_sale_from_event(event)
|
29
7
|
|
30
|
-
|
31
|
-
sale.fee_amount = charge.fee
|
32
|
-
else
|
33
|
-
balance = Stripe::BalanceTransaction.retrieve(charge.balance_transaction, secret_key)
|
34
|
-
sale.fee_amount = balance.fee
|
35
|
-
end
|
8
|
+
return unless sale
|
36
9
|
|
37
10
|
sale.save!
|
38
11
|
sale.finish!
|
data/lib/payola.rb
CHANGED
@@ -4,6 +4,16 @@ require 'stripe_event'
|
|
4
4
|
require 'jquery-rails'
|
5
5
|
|
6
6
|
module Payola
|
7
|
+
|
8
|
+
DEFAULT_EMAILS = {
|
9
|
+
receipt: [ 'payola.sale.finished', 'Payola::ReceiptMailer', :receipt ],
|
10
|
+
refund: [ 'charge.refunded', 'Payola::ReceiptMailer', :refund ],
|
11
|
+
admin_receipt: [ 'payola.sale.finished', 'Payola::AdminMailer', :receipt ],
|
12
|
+
admin_dispute: [ 'dispute.created', 'Payola::AdminMailer', :dispute ],
|
13
|
+
admin_refund: [ 'payola.sale.refunded', 'Payola::AdminMailer', :refund ],
|
14
|
+
admin_failure: [ 'payola.sale.failed', 'Payola::AdminMailer', :failure ],
|
15
|
+
}
|
16
|
+
|
7
17
|
class << self
|
8
18
|
attr_accessor :publishable_key,
|
9
19
|
:publishable_key_retriever,
|
@@ -17,6 +27,7 @@ module Payola
|
|
17
27
|
:charge_verifier,
|
18
28
|
:default_currency,
|
19
29
|
:additional_charge_attributes,
|
30
|
+
:guid_generator,
|
20
31
|
:pdf_receipt
|
21
32
|
|
22
33
|
def configure(&block)
|
@@ -74,6 +85,7 @@ module Payola
|
|
74
85
|
self.subscribables = {}
|
75
86
|
self.additional_charge_attributes = lambda { |sale, customer| { } }
|
76
87
|
self.pdf_receipt = false
|
88
|
+
self.guid_generator = lambda { SecureRandom.random_number(1_000_000_000).to_s(32) }
|
77
89
|
end
|
78
90
|
|
79
91
|
def register_sellable(klass)
|
@@ -85,20 +97,10 @@ module Payola
|
|
85
97
|
end
|
86
98
|
|
87
99
|
def send_email_for(*emails)
|
88
|
-
possible_emails = {
|
89
|
-
receipt: [ 'payola.sale.finished', Payola::ReceiptMailer, :receipt ],
|
90
|
-
refund: [ 'charge.refunded', Payola::ReceiptMailer, :refund ],
|
91
|
-
admin_receipt: [ 'payola.sale.finished', Payola::AdminMailer, :receipt ],
|
92
|
-
admin_dispute: [ 'dispute.created', Payola::AdminMailer, :dispute ],
|
93
|
-
admin_refund: [ 'payola.sale.refunded', Payola::AdminMailer, :refund ],
|
94
|
-
admin_failure: [ 'payola.sale.failed', Payola::AdminMailer, :failure ],
|
95
|
-
}
|
96
|
-
|
97
100
|
emails.each do |email|
|
98
|
-
spec =
|
101
|
+
spec = DEFAULT_EMAILS[email].dup
|
99
102
|
if spec
|
100
103
|
Payola.subscribe(spec.shift) do |sale|
|
101
|
-
|
102
104
|
if sale.is_a?(Stripe::Event)
|
103
105
|
sale = Payola::Sale.find_by!(stripe_id: sale.data.object.id)
|
104
106
|
end
|
data/lib/payola/version.rb
CHANGED
data/spec/concerns/plan_spec.rb
CHANGED
@@ -18,11 +18,6 @@ module Payola
|
|
18
18
|
expect(subscription_plan.valid?).to be false
|
19
19
|
end
|
20
20
|
|
21
|
-
it "should validate interval_count" do
|
22
|
-
subscription_plan = build(:subscription_plan, interval_count: nil)
|
23
|
-
expect(subscription_plan.valid?).to be false
|
24
|
-
end
|
25
|
-
|
26
21
|
it "should validate stripe_id" do
|
27
22
|
subscription_plan = build(:subscription_plan, stripe_id: nil)
|
28
23
|
expect(subscription_plan.valid?).to be false
|
@@ -13,7 +13,16 @@ module Payola
|
|
13
13
|
subscription.should_receive(:save).and_return(true)
|
14
14
|
subscription.should_receive(:guid).at_least(1).times.and_return(1)
|
15
15
|
|
16
|
-
CreateSubscription.should_receive(:call).
|
16
|
+
CreateSubscription.should_receive(:call).with(
|
17
|
+
'plan_class' => 'subscription_plan',
|
18
|
+
'plan_id' => @plan.id.to_s,
|
19
|
+
'controller' => 'payola/subscriptions',
|
20
|
+
'action' => 'create',
|
21
|
+
'plan' => @plan,
|
22
|
+
'coupon' => nil,
|
23
|
+
'affiliate' => nil
|
24
|
+
).and_return(subscription)
|
25
|
+
|
17
26
|
Payola.should_receive(:queue!)
|
18
27
|
post :create, plan_class: @plan.plan_class, plan_id: @plan.id, use_route: :payola
|
19
28
|
|
@@ -13,7 +13,16 @@ module Payola
|
|
13
13
|
sale.should_receive(:save).and_return(true)
|
14
14
|
sale.should_receive(:guid).at_least(1).times.and_return('blah')
|
15
15
|
|
16
|
-
CreateSale.should_receive(:call).
|
16
|
+
CreateSale.should_receive(:call).with(
|
17
|
+
'product_class' => 'product',
|
18
|
+
'permalink' => @product.permalink,
|
19
|
+
'controller' => 'payola/transactions',
|
20
|
+
'action' => 'create',
|
21
|
+
'product' => @product,
|
22
|
+
'coupon' => nil,
|
23
|
+
'affiliate' => nil
|
24
|
+
).and_return(sale)
|
25
|
+
|
17
26
|
Payola.should_receive(:queue!)
|
18
27
|
post :create, product_class: @product.product_class, permalink: @product.permalink, use_route: :payola
|
19
28
|
|