effective_orders 4.0.0beta4 → 4.0.0beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -10
- data/app/assets/javascripts/effective_orders.js +3 -1
- data/app/assets/javascripts/effective_orders/customers.js.coffee +10 -15
- data/app/assets/javascripts/effective_orders/providers/stripe.js.coffee +4 -14
- data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +18 -35
- data/app/assets/stylesheets/effective_orders.scss +0 -1
- data/app/controllers/admin/orders_controller.rb +1 -1
- data/app/datatables/effective_order_items_datatable.rb +2 -2
- data/app/helpers/effective_orders_helper.rb +3 -3
- data/app/helpers/effective_paypal_helper.rb +1 -1
- data/app/helpers/effective_subscriptions_helper.rb +7 -11
- data/app/mailers/effective/orders_mailer.rb +2 -2
- data/app/models/concerns/acts_as_purchasable.rb +3 -6
- data/app/models/concerns/acts_as_subscribable.rb +34 -20
- data/app/models/effective/cart.rb +1 -1
- data/app/models/effective/cart_item.rb +9 -5
- data/app/models/effective/customer.rb +4 -2
- data/app/models/effective/order.rb +2 -2
- data/app/models/effective/order_item.rb +3 -3
- data/app/models/effective/product.rb +3 -3
- data/app/models/effective/subscripter.rb +4 -4
- data/app/models/effective/subscription.rb +3 -14
- data/app/views/admin/orders/_order_item_fields.html.haml +1 -1
- data/app/views/effective/carts/_cart.html.haml +1 -1
- data/app/views/effective/customers/_customer.html.haml +2 -9
- data/app/views/effective/customers/_fields.html.haml +6 -9
- data/app/views/effective/customers/_form.html.haml +3 -4
- data/app/views/effective/orders/_order_items.html.haml +1 -1
- data/app/views/effective/orders/moneris/_form.html.haml +1 -1
- data/app/views/effective/subscriptions/_fields.html.haml +5 -9
- data/app/views/effective/subscriptions/_plan.html.haml +16 -17
- data/config/effective_orders.rb +18 -12
- data/db/migrate/01_create_effective_orders.rb.erb +2 -5
- data/lib/effective_orders.rb +22 -7
- data/lib/effective_orders/version.rb +1 -1
- data/lib/generators/templates/effective_orders_mailer_preview.rb +4 -4
- data/lib/tasks/effective_orders_tasks.rake +9 -9
- metadata +2 -3
- data/app/assets/stylesheets/effective_orders/_subscriptions.scss +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd54f1217fe33988f924bf40803efd702916d971
|
4
|
+
data.tar.gz: 291f290fd3cf4b8636ce5103a86b976760e12122
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 546166ac0b6f8948af732a82bdd2ba8b2e59f11ce6f734d10b1d6dd49350926d764331fe179d71f01a3a9d4510786254dfd6bbfeac0cffb992b821d03ea70c6c
|
7
|
+
data.tar.gz: 1d1602f8456cbf830c82ca6f298b85ca465b384240a5867479df1280a7384c1693e5b1970ff21e9b353bba3371268da37fb21ae4fb17a82bef993be0c74c5418
|
data/README.md
CHANGED
@@ -118,12 +118,12 @@ class Product < ActiveRecord::Base
|
|
118
118
|
acts_as_purchasable
|
119
119
|
|
120
120
|
# Attributes
|
121
|
-
#
|
121
|
+
# name :string
|
122
122
|
# price :integer, default: 0
|
123
123
|
# tax_exempt :boolean, default: false
|
124
124
|
# timestamps
|
125
125
|
|
126
|
-
validates_presence_of :
|
126
|
+
validates_presence_of :name
|
127
127
|
validates_numericality_of :price, greater_than_or_equal_to: 0
|
128
128
|
end
|
129
129
|
```
|
@@ -134,7 +134,7 @@ The database migration will look like the following:
|
|
134
134
|
class CreateProducts < ActiveRecord::Migration
|
135
135
|
def self.up
|
136
136
|
create_table :products do |t|
|
137
|
-
t.string :
|
137
|
+
t.string :name
|
138
138
|
t.integer :price, :default=>0
|
139
139
|
t.boolean :tax_exempt, :default=>false
|
140
140
|
t.datetime :updated_at
|
@@ -160,7 +160,7 @@ This is available for simple_form, formtastic and Rails default FormBuilder.
|
|
160
160
|
|
161
161
|
```haml
|
162
162
|
= simple_form_for(@product) do |f|
|
163
|
-
= f.input :
|
163
|
+
= f.input :name
|
164
164
|
= f.input :tax_exempt
|
165
165
|
= f.input :price, as: :effective_price
|
166
166
|
= f.button :submit
|
@@ -185,7 +185,7 @@ or
|
|
185
185
|
So back on the Product#show page, we will render the product with an Add To Cart link
|
186
186
|
|
187
187
|
```haml
|
188
|
-
%h4= @product
|
188
|
+
%h4= @product
|
189
189
|
%p= price_to_currency(@product.price)
|
190
190
|
%p= link_to_add_to_cart(@product, class: 'btn btn-primary', label: 'Add To My Shopping Cart')
|
191
191
|
```
|
@@ -570,6 +570,24 @@ render_datatable @datatable
|
|
570
570
|
Please refer to [effective_datatables](https://github.com/code-and-effect/effective_datatables/) for more information about that gem.
|
571
571
|
|
572
572
|
|
573
|
+
### Subscriptions
|
574
|
+
|
575
|
+
All subscriptions are completed via stripe subscriptions.
|
576
|
+
|
577
|
+
There is a hardcoded trial mode, that does not reach out to stripe.
|
578
|
+
|
579
|
+
Every `acts_as_subscribable` object starts as trial mode.
|
580
|
+
|
581
|
+
Every `acts_as_subscribable_buyer` gets a single stripe subscription, and then can buy quantities of stripe products
|
582
|
+
|
583
|
+
|
584
|
+
#### Stripe setup
|
585
|
+
|
586
|
+
Create a Product with the name you want customers to see on their receipts
|
587
|
+
|
588
|
+
Yearly and a Monthly per team
|
589
|
+
|
590
|
+
|
573
591
|
### Admin Screen
|
574
592
|
|
575
593
|
To use the Admin screen, please also install the effective_datatables gem:
|
@@ -598,16 +616,16 @@ render_datatable @datatable
|
|
598
616
|
|
599
617
|
## Rake Tasks
|
600
618
|
|
601
|
-
### Overwrite order item
|
619
|
+
### Overwrite order item names
|
602
620
|
|
603
|
-
When an order is purchased, the `
|
621
|
+
When an order is purchased, the `purchasable_name()` of each `acts_as_purchasable` object is saved to the database. Normally this is just `to_s`.
|
604
622
|
|
605
|
-
If you change the output of `acts_as_purchasable`.`
|
623
|
+
If you change the output of `acts_as_purchasable`.`purchasable_name`, any existing order items will remain unchanged.
|
606
624
|
|
607
|
-
Run this script to overwrite all saved order item
|
625
|
+
Run this script to overwrite all saved order item names with the current `acts_as_purchasable`.`purchasable_name`.
|
608
626
|
|
609
627
|
```ruby
|
610
|
-
rake effective_orders:
|
628
|
+
rake effective_orders:overwrite_order_item_names
|
611
629
|
```
|
612
630
|
|
613
631
|
## Testing in Development
|
@@ -1,25 +1,22 @@
|
|
1
1
|
stripeCustomerChangeCardHandler = (key, form) ->
|
2
2
|
StripeCheckout.configure
|
3
3
|
key: key
|
4
|
-
closed: ->
|
5
|
-
form.find("input[type='submit']").removeAttr('disabled')
|
6
|
-
$('input[data-disable-with]').each -> try $.rails.enableFormElement($(this))
|
4
|
+
closed: -> EffectiveBootstrap.enable(form) unless form.hasClass('stripe-success')
|
7
5
|
token: (token, args) ->
|
8
6
|
if token.error
|
9
|
-
|
10
|
-
$('input[data-disable-with]').each -> try $.rails.enableFormElement($(this))
|
7
|
+
message = "An error ocurred when contacting Stripe. Your card has not been charged. Your subscription has not changed. Please refresh the page and try again. #{token.error.message}"
|
11
8
|
|
12
|
-
|
9
|
+
form.removeClass('stripe-success')
|
10
|
+
form.find('.effective-orders-customer').find('.invalid-feedback').html(message).show()
|
11
|
+
alert(message)
|
13
12
|
else
|
14
13
|
form.find("input[name$='[stripe_token]']").val('' + token['id'])
|
15
14
|
|
16
|
-
customer = form.find('.effective-orders-customer')
|
17
|
-
customer.find('.
|
15
|
+
$customer = form.find('.effective-orders-customer')
|
16
|
+
$customer.find('.payment-status').html("**** **** **** #{token.card.last4} #{token.card.brand} #{token.card.exp_month}/#{token.card.exp_year}")
|
18
17
|
|
19
|
-
if customer.data('submit')
|
20
|
-
form.
|
21
|
-
$('input[data-disable-with]').each -> try $.rails.disableFormElement($(this))
|
22
|
-
form.submit()
|
18
|
+
if $customer.data('submit')
|
19
|
+
form.addClass('stripe-success').submit()
|
23
20
|
|
24
21
|
# When we click 'Change credit card', make sure the form collects a credit card
|
25
22
|
$(document).on 'click', '.effective-orders-customer .btn-change-card', (event) ->
|
@@ -28,9 +25,7 @@ $(document).on 'click', '.effective-orders-customer .btn-change-card', (event) -
|
|
28
25
|
$form = $(event.currentTarget).closest('form')
|
29
26
|
stripe = $(event.currentTarget).closest('.effective-orders-customer').data('stripe')
|
30
27
|
|
31
|
-
|
32
|
-
$form.find("input[type='submit']").prop('disabled', true)
|
33
|
-
$('input[data-disable-with]').each -> try $.rails.disableFormElement($(this))
|
28
|
+
EffectiveBootstrap.submitting($form)
|
34
29
|
|
35
30
|
stripeCustomerChangeCardHandler(stripe.key, $form).open
|
36
31
|
image: stripe.image
|
@@ -1,32 +1,22 @@
|
|
1
1
|
stripeCheckoutHandler = (key, form) ->
|
2
2
|
StripeCheckout.configure
|
3
3
|
key: key
|
4
|
-
closed: ->
|
5
|
-
form.find("input[type='submit']").removeAttr('disabled')
|
6
|
-
$('input[data-disable-with]').each -> try $.rails.enableFormElement($(this))
|
4
|
+
closed: -> EffectiveBootstrap.enable(form) unless form.hasClass('stripe-success')
|
7
5
|
token: (token, args) ->
|
8
6
|
if token.error
|
9
|
-
form.find("input[type='submit']").removeAttr('disabled')
|
10
|
-
$('input[data-disable-with]').each -> try $.rails.enableFormElement($(this))
|
11
|
-
|
12
7
|
alert("An error ocurred when contacting Stripe. Your card has not been charged. Please refresh the page and try again. #{token.error.message}")
|
13
8
|
else
|
14
9
|
form.find("input[name$='[stripe_token]']").val('' + token['id'])
|
10
|
+
form.addClass('stripe-success').submit()
|
15
11
|
|
16
|
-
|
17
|
-
$('input[data-disable-with]').each -> try $.rails.disableFormElement($(this))
|
18
|
-
form.submit()
|
19
|
-
|
20
|
-
$(document).on 'click', "#effective-orders-new-charge-form form input[type='submit']", (event) ->
|
12
|
+
$(document).on 'click', "#effective-orders-new-charge-form form [type='submit']", (event) ->
|
21
13
|
event.preventDefault()
|
22
14
|
|
23
15
|
obj = $('#effective-orders-new-charge-form')
|
24
16
|
$form = obj.find('form').first()
|
25
17
|
stripe = obj.data('stripe')
|
26
18
|
|
27
|
-
|
28
|
-
$form.find("input[type='submit']").prop('disabled', true)
|
29
|
-
$('input[data-disable-with]').each -> try $.rails.disableFormElement($(this))
|
19
|
+
EffectiveBootstrap.submitting($form)
|
30
20
|
|
31
21
|
stripeCheckoutHandler(stripe.key, $form).open
|
32
22
|
image: stripe.image
|
@@ -1,35 +1,29 @@
|
|
1
1
|
stripeSubscriptionHandler = (key, form) ->
|
2
2
|
StripeCheckout.configure
|
3
3
|
key: key
|
4
|
-
closed: ->
|
5
|
-
form.find("input[type='submit']").removeAttr('disabled')
|
6
|
-
$('input[data-disable-with]').each -> try $.rails.enableFormElement($(this))
|
4
|
+
closed: -> EffectiveBootstrap.enable(form) unless form.hasClass('stripe-success')
|
7
5
|
token: (token, args) ->
|
8
6
|
if token.error
|
9
|
-
|
10
|
-
$('input[data-disable-with]').each -> try $.rails.enableFormElement($(this))
|
7
|
+
message = "An error ocurred when contacting Stripe. Your card has not been charged. Your subscription has not changed. Please refresh the page and try again. #{token.error.message}"
|
11
8
|
|
12
|
-
|
9
|
+
form.removeClass('stripe-success')
|
10
|
+
form.find('.effective-orders-stripe-plans').find('.invalid-feedback').html(message).show()
|
11
|
+
alert(message)
|
13
12
|
else
|
14
13
|
form.find("input[name$='[stripe_token]']").val('' + token['id'])
|
14
|
+
form.addClass('stripe-success').submit()
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
form.submit()
|
19
|
-
|
20
|
-
$(document).on 'click', "input[type='submit'].effective-orders-subscripter-token-required", (event) ->
|
16
|
+
# When I submit a for that needs a subscripter token, do the stripe thing.
|
17
|
+
$(document).on 'click', ".effective-orders-subscripter-token-required[type='submit']", (event) ->
|
21
18
|
event.preventDefault()
|
22
|
-
|
23
|
-
$submit = $(event.currentTarget)
|
24
|
-
$form = $submit.closest('form')
|
25
|
-
|
26
|
-
# Disable the form
|
27
|
-
$submit.prop('disabled', true)
|
28
|
-
$('input[data-disable-with]').each -> try $.rails.disableFormElement($(this))
|
19
|
+
$form = $(event.currentTarget).closest('form')
|
29
20
|
|
30
21
|
# Get the stripe data
|
31
22
|
$plans = $form.find('.effective-orders-stripe-plans').first()
|
32
23
|
selected_plan_id = $plans.find("input[name$='[subscripter][stripe_plan_id]']:checked").val()
|
24
|
+
return unless $plans.length > 0 && selected_plan_id.length > 0
|
25
|
+
|
26
|
+
EffectiveBootstrap.submitting($form)
|
33
27
|
|
34
28
|
stripe = $plans.data('stripe')
|
35
29
|
plan = stripe.plans.find (plan, _) => plan.id == selected_plan_id
|
@@ -42,32 +36,21 @@ $(document).on 'click', "input[type='submit'].effective-orders-subscripter-token
|
|
42
36
|
amount: plan.amount
|
43
37
|
panelLabel: "{{amount}}#{plan.occurrence} Go!"
|
44
38
|
|
45
|
-
# When a plan
|
46
|
-
# Set the submit button's class if a customer token is required
|
39
|
+
# When I click on a stripe plan ID radio button, add .effective-orders-subscripter-token-required to the form if required
|
47
40
|
$(document).on 'change', "input[name$='[subscripter][stripe_plan_id]']", (event) ->
|
48
41
|
$plan = $(event.currentTarget)
|
49
42
|
return unless $plan.is(':checked')
|
50
43
|
|
51
|
-
$plans = $plan.closest('.effective-orders-stripe-plans').first()
|
52
|
-
selected_class = $plans.data('selected-class')
|
53
44
|
selected_plan_id = $plan.val()
|
54
45
|
|
55
|
-
$plans.
|
56
|
-
if $(item).is(':checked')
|
57
|
-
$(item).siblings('.panel').addClass(selected_class)
|
58
|
-
else
|
59
|
-
$(item).siblings('.panel').removeClass(selected_class)
|
60
|
-
|
46
|
+
$plans = $plan.closest('.effective-orders-stripe-plans').first()
|
61
47
|
plan = $plans.data('stripe').plans.find (plan, _) => plan.id == selected_plan_id
|
48
|
+
|
62
49
|
token_required = $plans.data('stripe').token_required
|
63
50
|
|
64
51
|
if (plan.amount || 0) > 0 && token_required
|
65
|
-
$plans.closest('form').find("input[type='submit']").addClass('effective-orders-subscripter-token-required')
|
52
|
+
$plans.closest('form').find("input[type='submit'],button[type='submit']").addClass('effective-orders-subscripter-token-required')
|
66
53
|
else
|
67
|
-
$plans.closest('form').find("input[type='submit']").removeClass('effective-orders-subscripter-token-required')
|
54
|
+
$plans.closest('form').find("input[type='submit'],button[type='submit']").removeClass('effective-orders-subscripter-token-required')
|
68
55
|
|
69
|
-
|
70
|
-
$(document).on 'click', '.effective-orders-stripe-plan .btn-select-plan', (event) ->
|
71
|
-
val = $(event.currentTarget).closest('.effective-orders-stripe-plan').find('input:radio').val()
|
72
|
-
$(event.currentTarget).closest('.effective-orders-stripe-plans').find('input:radio').val([val]).trigger('change')
|
73
|
-
false
|
56
|
+
true
|
@@ -188,7 +188,7 @@ module Admin
|
|
188
188
|
:payment_provider, :payment_card, :payment, :send_mark_as_paid_email_to_buyer,
|
189
189
|
order_items_attributes: [
|
190
190
|
:quantity, :_destroy, purchasable_attributes: [
|
191
|
-
:
|
191
|
+
:name, :price, :tax_exempt
|
192
192
|
]
|
193
193
|
]
|
194
194
|
)
|
@@ -37,8 +37,8 @@ class EffectiveOrderItemsDatatable < Effective::Datatable
|
|
37
37
|
order_item[:state] || 'abandoned'
|
38
38
|
end
|
39
39
|
|
40
|
-
col :
|
41
|
-
order_item.quantity == 1 ? order_item.
|
40
|
+
col :name do |order_item|
|
41
|
+
order_item.quantity == 1 ? order_item.name : "#{order_item.name} (#{order_item.quantity} purchased)"
|
42
42
|
end
|
43
43
|
|
44
44
|
# col :subtotal, as: :price
|
@@ -14,9 +14,9 @@ module EffectiveOrdersHelper
|
|
14
14
|
order_item_list = content_tag(:ul) do
|
15
15
|
order.order_items.map do |item|
|
16
16
|
content_tag(:li) do
|
17
|
-
|
18
|
-
"#{item.quantity}x #{
|
19
|
-
|
17
|
+
names = item.name.split('<br>')
|
18
|
+
"#{item.quantity}x #{names.first} for #{price_to_currency(item.price)}".tap do |output|
|
19
|
+
names[1..-1].each { |line| output << "<br>#{line}" }
|
20
20
|
end.html_safe
|
21
21
|
end
|
22
22
|
end.join.html_safe
|
@@ -37,7 +37,7 @@ module EffectivePaypalHelper
|
|
37
37
|
|
38
38
|
order.order_items.each_with_index do |item, x|
|
39
39
|
values["item_number_#{x+1}"] = x+1
|
40
|
-
values["item_name_#{x+1}"] = item.
|
40
|
+
values["item_name_#{x+1}"] = item.name
|
41
41
|
values["quantity_#{x+1}"] = item.quantity
|
42
42
|
values["amount_#{x+1}"] = '%.2f' % (item.price / 100.0)
|
43
43
|
values["tax_#{x+1}"] = '%.2f' % ((item.tax / 100.0) / item.quantity) # Tax for 1 of these items
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module EffectiveSubscriptionsHelper
|
2
2
|
|
3
3
|
def effective_customer_fields(form, submit: true)
|
4
|
-
raise 'expected
|
4
|
+
raise 'expected an Effective::FormBuilder object' unless form.class.name == 'Effective::FormBuilder'
|
5
5
|
raise 'form object must be an Effective::Subscripter object' unless form.object.class.name == 'Effective::Subscripter'
|
6
6
|
|
7
7
|
render(
|
@@ -19,11 +19,11 @@ module EffectiveSubscriptionsHelper
|
|
19
19
|
)
|
20
20
|
end
|
21
21
|
|
22
|
-
def stripe_plans_collection(form, include_trial: nil
|
23
|
-
raise 'expected
|
22
|
+
def stripe_plans_collection(form, include_trial: nil)
|
23
|
+
raise 'expected an Effective::FormBuilder object' unless form.class.name == 'Effective::FormBuilder'
|
24
24
|
raise 'form object must be an acts_as_subscribable object' unless form.object.subscribable.subscripter.present?
|
25
25
|
|
26
|
-
include_trial = form.object.subscribable.
|
26
|
+
include_trial = form.object.subscribable.trialing? if include_trial.nil?
|
27
27
|
|
28
28
|
plans = include_trial ? EffectiveOrders.stripe_plans : EffectiveOrders.stripe_plans.except('trial')
|
29
29
|
plans = plans.values.sort { |x, y| (amount = x[:amount] <=> y[:amount]) != 0 ? amount : x[:name] <=> y[:name] }
|
@@ -41,7 +41,6 @@ module EffectiveSubscriptionsHelper
|
|
41
41
|
f: form,
|
42
42
|
plan: plan,
|
43
43
|
selected: Array(form.object.stripe_plan_id).include?(plan[:id]),
|
44
|
-
selected_class: selected_class,
|
45
44
|
subscribable: form.object.subscribable,
|
46
45
|
subscribed: form.object.subscribable.subscribed?(plan[:id])
|
47
46
|
})
|
@@ -50,8 +49,8 @@ module EffectiveSubscriptionsHelper
|
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
|
-
def effective_subscription_fields(form, label: false, required: true, include_trial: nil
|
54
|
-
raise 'expected
|
52
|
+
def effective_subscription_fields(form, label: false, required: true, include_trial: nil)
|
53
|
+
raise 'expected an Effective::FormBuilder object' unless form.class.name == 'Effective::FormBuilder'
|
55
54
|
raise 'form object must be an acts_as_subscribable object' unless form.object.subscripter.present?
|
56
55
|
|
57
56
|
render(
|
@@ -61,17 +60,14 @@ module EffectiveSubscriptionsHelper
|
|
61
60
|
label: label,
|
62
61
|
required: required,
|
63
62
|
include_trial: include_trial,
|
64
|
-
item_wrapper_class: item_wrapper_class,
|
65
|
-
selected_class: selected_class,
|
66
63
|
stripe: {
|
67
|
-
email: form.object.
|
64
|
+
email: form.object.subscribable_buyer.email,
|
68
65
|
image: stripe_site_image_url,
|
69
66
|
key: EffectiveOrders.stripe[:publishable_key],
|
70
67
|
name: EffectiveOrders.stripe[:site_title],
|
71
68
|
plans: EffectiveOrders.stripe_plans.values,
|
72
69
|
token_required: form.object.subscripter.token_required?
|
73
70
|
},
|
74
|
-
wrapper_class: wrapper_class
|
75
71
|
}
|
76
72
|
)
|
77
73
|
end
|
@@ -116,7 +116,7 @@ module Effective
|
|
116
116
|
@subscribable = subscribable
|
117
117
|
|
118
118
|
mail(
|
119
|
-
to: @subscribable.
|
119
|
+
to: @subscribable.subscribable_buyer.email,
|
120
120
|
from: EffectiveOrders.mailer[:default_from],
|
121
121
|
subject: subject_for_subscription_trial_expiring(@subscribable)
|
122
122
|
)
|
@@ -129,7 +129,7 @@ module Effective
|
|
129
129
|
@subscribable = subscribable
|
130
130
|
|
131
131
|
mail(
|
132
|
-
to: @subscribable.
|
132
|
+
to: @subscribable.subscribable_buyer.email,
|
133
133
|
from: EffectiveOrders.mailer[:default_from],
|
134
134
|
subject: subject_for_subscription_trial_expired(@subscribable)
|
135
135
|
)
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module ActsAsPurchasable
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
mattr_accessor :descendants
|
5
|
-
|
6
4
|
module ActiveRecord
|
7
5
|
def acts_as_purchasable(*options)
|
8
6
|
@acts_as_purchasable = options || []
|
@@ -20,7 +18,7 @@ module ActsAsPurchasable
|
|
20
18
|
end
|
21
19
|
|
22
20
|
included do
|
23
|
-
belongs_to :purchased_order, class_name: 'Effective::Order' # Set when purchased
|
21
|
+
belongs_to :purchased_order, class_name: 'Effective::Order', optional: true # Set when purchased
|
24
22
|
|
25
23
|
has_many :cart_items, as: :purchasable, dependent: :delete_all, class_name: 'Effective::CartItem'
|
26
24
|
|
@@ -76,8 +74,8 @@ module ActsAsPurchasable
|
|
76
74
|
end
|
77
75
|
end
|
78
76
|
|
79
|
-
def
|
80
|
-
|
77
|
+
def purchasable_name
|
78
|
+
to_s
|
81
79
|
end
|
82
80
|
|
83
81
|
def tax_exempt
|
@@ -113,4 +111,3 @@ module ActsAsPurchasable
|
|
113
111
|
end
|
114
112
|
|
115
113
|
end
|
116
|
-
|
@@ -6,6 +6,11 @@ module ActsAsSubscribable
|
|
6
6
|
module ActiveRecord
|
7
7
|
def acts_as_subscribable(*options)
|
8
8
|
@acts_as_subscribable = options || []
|
9
|
+
|
10
|
+
instance = new()
|
11
|
+
raise 'must respond to trialing_until' unless instance.respond_to?(:trialing_until)
|
12
|
+
raise 'must respond to subscription_status' unless instance.respond_to?(:subscription_status)
|
13
|
+
|
9
14
|
include ::ActsAsSubscribable
|
10
15
|
(ActsAsSubscribable.descendants ||= []) << self
|
11
16
|
end
|
@@ -15,17 +20,29 @@ module ActsAsSubscribable
|
|
15
20
|
has_one :subscription, as: :subscribable, class_name: 'Effective::Subscription'
|
16
21
|
has_one :customer, through: :subscription, class_name: 'Effective::Customer'
|
17
22
|
|
23
|
+
before_validation(if: -> { trialing_until.blank? && EffectiveOrders.trial? }) do
|
24
|
+
self.trialing_until = (Time.zone.now + EffectiveOrders.trial.fetch(:length)).end_of_day
|
25
|
+
end
|
26
|
+
|
27
|
+
validates :trialing_until, presence: true, if: -> { EffectiveOrders.trial? }
|
28
|
+
validates :subscription_status, inclusion: { allow_nil: true, in: EffectiveOrders::STATUSES.keys }
|
29
|
+
|
18
30
|
validates :subscripter, associated: true
|
19
31
|
|
20
|
-
scope :
|
21
|
-
scope :
|
32
|
+
scope :trialing, -> { where(subscription_status: nil).where('trialing_until > ?', Time.zone.now) }
|
33
|
+
scope :trial_past_due, -> { where(subscription_status: nil).where('trialing_until < ?', Time.zone.now) }
|
34
|
+
scope :not_trialing, -> { where.not(subscription_status: nil) }
|
35
|
+
|
36
|
+
scope :subscribed, -> { where(subscription_status: EffectiveOrders::ACTIVE) }
|
37
|
+
scope :subscription_past_due, -> { where(subscription_status: EffectiveOrders::PAST_DUE) }
|
38
|
+
scope :not_subscribed, -> { where(subscription_status: nil) }
|
22
39
|
end
|
23
40
|
|
24
41
|
module ClassMethods
|
25
42
|
end
|
26
43
|
|
27
44
|
def subscripter
|
28
|
-
@_effective_subscripter ||= Effective::Subscripter.new(subscribable: self, user:
|
45
|
+
@_effective_subscripter ||= Effective::Subscripter.new(subscribable: self, user: subscribable_buyer)
|
29
46
|
end
|
30
47
|
|
31
48
|
def subscripter=(atts)
|
@@ -33,35 +50,32 @@ module ActsAsSubscribable
|
|
33
50
|
end
|
34
51
|
|
35
52
|
def subscribed?(stripe_plan_id = nil)
|
36
|
-
|
37
|
-
|
38
|
-
subscription.present? # Subscribed to any subscription?
|
39
|
-
when (EffectiveOrders.stripe_plans['trial'] || {})[:id]
|
40
|
-
subscription.blank? || subscription.new_record? || subscription.stripe_plan_id == stripe_plan_id
|
41
|
-
else
|
42
|
-
subscription && subscription.persisted? && subscription.errors.blank? && subscription.stripe_plan_id == stripe_plan_id
|
43
|
-
end
|
53
|
+
return false if subscription_status.blank?
|
54
|
+
stripe_plan_id ? (subscription&.stripe_plan_id == stripe_plan_id) : true
|
44
55
|
end
|
45
56
|
|
46
57
|
def subscription_active?
|
47
|
-
|
58
|
+
subscribed? && subscription_status == EffectiveOrders::ACTIVE
|
59
|
+
end
|
60
|
+
|
61
|
+
def subscription_past_due?
|
62
|
+
subscribed? && subscription_status == EffectiveOrders::PAST_DUE
|
48
63
|
end
|
49
64
|
|
50
65
|
def trialing?
|
51
|
-
|
66
|
+
subscription_status.blank?
|
52
67
|
end
|
53
68
|
|
54
|
-
def
|
55
|
-
trialing? && Time.zone.now
|
69
|
+
def trial_active?
|
70
|
+
trialing? && trialing_until > Time.zone.now
|
56
71
|
end
|
57
72
|
|
58
|
-
def
|
59
|
-
|
60
|
-
((created_at || Time.zone.now) + EffectiveOrders.subscriptions[:trial_period]).beginning_of_day
|
73
|
+
def trial_past_due?
|
74
|
+
trialing? && trialing_until < Time.zone.now
|
61
75
|
end
|
62
76
|
|
63
|
-
def
|
64
|
-
raise 'acts_as_subscribable object requires the
|
77
|
+
def subscribable_buyer
|
78
|
+
raise 'acts_as_subscribable object requires the subscribable_buyer method be defined to return the User buying this item.'
|
65
79
|
end
|
66
80
|
|
67
81
|
end
|
@@ -39,7 +39,7 @@ module Effective
|
|
39
39
|
end
|
40
40
|
|
41
41
|
if item.quantity_enabled? && (existing ? existing.quantity : quantity) > item.quantity_remaining
|
42
|
-
raise EffectiveOrders::SoldOutException, "#{item.
|
42
|
+
raise EffectiveOrders::SoldOutException, "#{item.purchasable_name} is sold out"
|
43
43
|
end
|
44
44
|
|
45
45
|
existing ||= cart_items.build(purchasable: item, quantity: quantity, unique: (unique.to_s unless unique.kind_of?(Proc)))
|
@@ -12,6 +12,14 @@ module Effective
|
|
12
12
|
validates :purchasable, presence: true
|
13
13
|
validates :quantity, presence: true
|
14
14
|
|
15
|
+
def to_s
|
16
|
+
name || 'New Cart Item'
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
purchasable&.purchasable_name
|
21
|
+
end
|
22
|
+
|
15
23
|
def price
|
16
24
|
if (purchasable.price || 0).kind_of?(Integer)
|
17
25
|
purchasable.price || 0
|
@@ -20,12 +28,8 @@ module Effective
|
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
23
|
-
def title
|
24
|
-
purchasable.try(:title) || 'New Cart Item'
|
25
|
-
end
|
26
|
-
|
27
31
|
def tax_exempt
|
28
|
-
purchasable
|
32
|
+
purchasable&.tax_exempt || false
|
29
33
|
end
|
30
34
|
|
31
35
|
def subtotal
|
@@ -5,10 +5,12 @@ module Effective
|
|
5
5
|
attr_accessor :stripe_customer, :stripe_subscription
|
6
6
|
|
7
7
|
belongs_to :user
|
8
|
+
has_many :subscribables, as: :subscribable
|
9
|
+
|
8
10
|
has_many :subscriptions, class_name: 'Effective::Subscription', foreign_key: 'customer_id'
|
9
11
|
has_many :subscribables, through: :subscriptions, source: :subscribable
|
10
12
|
|
11
|
-
accepts_nested_attributes_for :subscriptions
|
13
|
+
#accepts_nested_attributes_for :subscriptions
|
12
14
|
|
13
15
|
# Attributes
|
14
16
|
# stripe_customer_id :string # cus_xja7acoa03
|
@@ -27,7 +29,7 @@ module Effective
|
|
27
29
|
|
28
30
|
validates :user, presence: true
|
29
31
|
validates :stripe_customer_id, presence: true
|
30
|
-
validates :status, if: -> { stripe_subscription_id.present? }, inclusion: { in:
|
32
|
+
validates :status, if: -> { stripe_subscription_id.present? }, inclusion: { in: EffectiveOrders::STATUSES.keys }
|
31
33
|
|
32
34
|
def self.for_user(user)
|
33
35
|
Effective::Customer.where(user: user).first_or_initialize
|
@@ -179,7 +179,7 @@ module Effective
|
|
179
179
|
self.note_internal ||= item.note_internal
|
180
180
|
|
181
181
|
item.order_items.select { |oi| oi.purchasable.kind_of?(Effective::Product) }.map do |oi|
|
182
|
-
product = Effective::Product.new(
|
182
|
+
product = Effective::Product.new(name: oi.purchasable.purchasable_name, price: oi.purchasable.price, tax_exempt: oi.purchasable.tax_exempt)
|
183
183
|
Effective::CartItem.new(quantity: oi.quantity, purchasable: product)
|
184
184
|
end
|
185
185
|
else
|
@@ -194,7 +194,7 @@ module Effective
|
|
194
194
|
|
195
195
|
retval = cart_items.map do |item|
|
196
196
|
order_items.build(
|
197
|
-
|
197
|
+
name: item.name,
|
198
198
|
quantity: item.quantity,
|
199
199
|
price: item.price,
|
200
200
|
tax_exempt: (item.tax_exempt || false),
|
@@ -9,7 +9,7 @@ module Effective
|
|
9
9
|
delegate :purchased?, :declined?, to: :order
|
10
10
|
|
11
11
|
# Attributes
|
12
|
-
#
|
12
|
+
# name :string
|
13
13
|
# quantity :integer
|
14
14
|
# price :integer, default: 0
|
15
15
|
# tax_exempt :boolean
|
@@ -18,7 +18,7 @@ module Effective
|
|
18
18
|
validates :purchasable, associated: true, presence: true
|
19
19
|
accepts_nested_attributes_for :purchasable
|
20
20
|
|
21
|
-
validates :
|
21
|
+
validates :name, presence: true
|
22
22
|
validates :quantity, presence: true, numericality: { greater_than: 0 }
|
23
23
|
validates :price, presence: true
|
24
24
|
validates :tax_exempt, inclusion: { in: [true, false] }
|
@@ -27,7 +27,7 @@ module Effective
|
|
27
27
|
scope :sold_by, lambda { |user| sold().where(seller_id: user.id) }
|
28
28
|
|
29
29
|
def to_s
|
30
|
-
(quantity || 0) > 1 ? "#{quantity}x #{
|
30
|
+
(quantity || 0) > 1 ? "#{quantity}x #{name}" : name
|
31
31
|
end
|
32
32
|
|
33
33
|
def subtotal
|
@@ -5,18 +5,18 @@ module Effective
|
|
5
5
|
acts_as_purchasable
|
6
6
|
|
7
7
|
# Attributes
|
8
|
-
#
|
8
|
+
# name :string
|
9
9
|
# price :integer, default: 0
|
10
10
|
# tax_exempt :boolean, default: false
|
11
11
|
#
|
12
12
|
# timestamps
|
13
13
|
|
14
|
-
validates :
|
14
|
+
validates :name, presence: true
|
15
15
|
validates :price, presence: true
|
16
16
|
validates :tax_exempt, inclusion: { in: [true, false] }
|
17
17
|
|
18
18
|
def to_s
|
19
|
-
|
19
|
+
name || 'New Product'
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
@@ -184,10 +184,10 @@ module Effective
|
|
184
184
|
end
|
185
185
|
|
186
186
|
# When upgrading a plan, invoice immediately.
|
187
|
-
if current_plan && current_plan[:id] != 'trial' && plan[:amount] > current_plan[:amount]
|
188
|
-
|
189
|
-
|
190
|
-
end
|
187
|
+
# if current_plan && current_plan[:id] != 'trial' && plan[:amount] > current_plan[:amount]
|
188
|
+
# Rails.logger.info " -> INVOICE GENERATED"
|
189
|
+
# Stripe::Invoice.create(customer: customer.stripe_customer_id).pay rescue false
|
190
|
+
# end
|
191
191
|
|
192
192
|
# Sync status
|
193
193
|
customer.status = customer.stripe_subscription.status
|
@@ -8,42 +8,31 @@ module Effective
|
|
8
8
|
belongs_to :subscribable, polymorphic: true
|
9
9
|
|
10
10
|
# Attributes
|
11
|
-
# stripe_plan_id :string
|
12
|
-
# status :string
|
13
|
-
#
|
11
|
+
# stripe_plan_id :string
|
14
12
|
# name :string
|
15
|
-
# price :integer, default: 0
|
16
13
|
#
|
17
14
|
# timestamps
|
18
15
|
|
19
16
|
before_validation(if: -> { plan && (stripe_plan_id_changed? || new_record?) }) do
|
20
17
|
self.name = "#{plan[:name]} #{plan[:description]}"
|
21
|
-
self.price = plan[:amount]
|
22
18
|
end
|
23
19
|
|
24
20
|
validates :customer, presence: true
|
25
21
|
validates :subscribable, presence: true
|
26
22
|
|
27
23
|
validates :stripe_plan_id, presence: true, inclusion: { in: EffectiveOrders.stripe_plans.except('trial').keys }
|
28
|
-
validates :status, presence: true, inclusion: { in: %w(active past_due) }
|
29
|
-
|
30
24
|
validates :name, presence: true
|
31
|
-
validates :price, numericality: { greater_than_or_equal_to: 0, only_integer: true }
|
32
25
|
|
33
26
|
def to_s
|
34
|
-
name
|
27
|
+
name || 'New Subscription'
|
35
28
|
end
|
36
29
|
|
37
30
|
def plan
|
38
31
|
EffectiveOrders.stripe_plans[stripe_plan_id]
|
39
32
|
end
|
40
33
|
|
41
|
-
def active?
|
42
|
-
status == 'active'
|
43
|
-
end
|
44
|
-
|
45
34
|
def <=>(other)
|
46
|
-
|
35
|
+
name.to_s <=> other&.name.to_s
|
47
36
|
end
|
48
37
|
|
49
38
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
.row.align-items-center
|
3
3
|
= f.fields_for :purchasable, (f.object.purchasable || Effective::Product.new) do |pf|
|
4
4
|
.col-md-1= f.number_field :quantity, input_html: { value: f.object.quantity || 1, min: 1 }
|
5
|
-
.col-md-4= pf.text_field :
|
5
|
+
.col-md-4= pf.text_field :name, maxlength: 255
|
6
6
|
.col-md-2= pf.price_field :price
|
7
7
|
|
8
8
|
- if defined?(EffectiveQbSync)
|
@@ -76,7 +76,7 @@
|
|
76
76
|
- if customer.stripe_subscription.items.present?
|
77
77
|
%tr
|
78
78
|
%th Plans
|
79
|
-
%td= tableize_hash(customer.stripe_subscription.items.inject({}) { |h, item| h[item.plan.
|
79
|
+
%td= tableize_hash(customer.stripe_subscription.items.inject({}) { |h, item| h[item.plan.nickname] = item.quantity; h }, th: false)
|
80
80
|
|
81
81
|
- if customer.subscriptions.present?
|
82
82
|
%table.table
|
@@ -118,7 +118,7 @@
|
|
118
118
|
= Time.zone.at(invoice.lines.first.period.end).strftime('%F')
|
119
119
|
%td
|
120
120
|
- invoice.lines.each do |line|
|
121
|
-
%p
|
121
|
+
%p= line.description
|
122
122
|
|
123
123
|
%td= price_to_currency(invoice.total)
|
124
124
|
|
@@ -130,12 +130,6 @@
|
|
130
130
|
%tr
|
131
131
|
%th Date
|
132
132
|
%td= Time.zone.at(customer.upcoming_invoice.date).strftime('%F')
|
133
|
-
%tr
|
134
|
-
%th Period
|
135
|
-
%td
|
136
|
-
= Time.zone.at(customer.upcoming_invoice.period_start).strftime('%F')
|
137
|
-
to
|
138
|
-
= Time.zone.at(customer.upcoming_invoice.period_end).strftime('%F')
|
139
133
|
%tr
|
140
134
|
%th Items
|
141
135
|
%td
|
@@ -143,7 +137,6 @@
|
|
143
137
|
%tbody
|
144
138
|
- customer.upcoming_invoice.lines.each do |line|
|
145
139
|
%tr
|
146
|
-
%td #{line.quantity}x #{line.plan.name}
|
147
140
|
%td #{Time.zone.at(line.period.start).strftime('%F')} to #{Time.zone.at(line.period.end).strftime('%F')}
|
148
141
|
%td= line.description
|
149
142
|
%td= price_to_currency(line.amount)
|
@@ -1,12 +1,9 @@
|
|
1
1
|
.effective-orders-customer{ data: { stripe: stripe.to_json, submit: submit.to_json } }
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
.card
|
3
|
+
.card-header Credit Card
|
4
|
+
.card-body
|
5
|
+
= f.static_field :stripe_token, label: false, class: 'payment-status', value: f.object.customer.payment_status
|
5
6
|
|
6
|
-
|
7
|
+
%p= link_to 'Update Card Details', '#', class: 'btn btn-primary btn-change-card'
|
7
8
|
|
8
|
-
-
|
9
|
-
= f.input :stripe_token, as: :effective_static_control, label: 'Credit Card', required: false,
|
10
|
-
value: f.object.customer.payment_status + ' ' + content_tag(:span, f.object.customer.active_card, class: 'active-card')
|
11
|
-
|
12
|
-
%p= link_to 'Update Card Details', '#', class: 'btn btn-default btn-change-card'
|
9
|
+
%p.invalid-feedback
|
@@ -1,13 +1,12 @@
|
|
1
1
|
= javascript_include_tag 'https://checkout.stripe.com/checkout.js'
|
2
2
|
|
3
|
-
=
|
4
|
-
= f.
|
3
|
+
= effective_form_with(model: subscripter, url: effective_orders.customer_settings_path, method: :put) do |f|
|
4
|
+
= f.hidden_field :stripe_token, value: nil
|
5
5
|
|
6
6
|
= render(f.object.customer) do
|
7
7
|
%h3.effective-heading Card
|
8
8
|
= effective_customer_fields(f)
|
9
9
|
|
10
|
-
.
|
11
|
-
= f.button :submit, 'Save', data: { disable_with: 'Saving...' }
|
10
|
+
= f.submit
|
12
11
|
|
13
12
|
|
@@ -19,7 +19,7 @@
|
|
19
19
|
|
20
20
|
- order.order_items.each_with_index do |item, x|
|
21
21
|
= hidden_field_tag("id#{x}", x)
|
22
|
-
= hidden_field_tag("description#{x}", item.
|
22
|
+
= hidden_field_tag("description#{x}", item.name)
|
23
23
|
= hidden_field_tag("quantity#{x}", item.quantity)
|
24
24
|
= hidden_field_tag("price#{x}", '%.2f' % (item.price / 100.0))
|
25
25
|
= hidden_field_tag("subtotal#{x}", '%.2f' % (item.subtotal / 100.0))
|
@@ -1,16 +1,12 @@
|
|
1
1
|
= javascript_include_tag 'https://checkout.stripe.com/checkout.js'
|
2
2
|
|
3
|
-
= form.
|
4
|
-
= fs.
|
3
|
+
= form.fields_for :subscripter, form.object.subscripter do |fs|
|
4
|
+
= fs.hidden_field :stripe_token, value: nil
|
5
5
|
|
6
6
|
- fs.object.stripe_plan_id ||= (fs.object.current_plan || {})[:id]
|
7
7
|
|
8
|
-
= fs.
|
8
|
+
= fs.radios :stripe_plan_id, stripe_plans_collection(fs, include_trial: include_trial),
|
9
9
|
label: label,
|
10
10
|
required: required,
|
11
|
-
|
12
|
-
|
13
|
-
item_wrapper_tag: 'div'
|
14
|
-
|
15
|
-
- if fs.object.customer.persisted?
|
16
|
-
= effective_customer_fields(fs)
|
11
|
+
cards: true,
|
12
|
+
wrapper: { class: 'effective-orders-stripe-plans', data: { stripe: stripe } }
|
@@ -1,21 +1,20 @@
|
|
1
|
-
.
|
2
|
-
|
3
|
-
|
4
|
-
%p= plan[:description]
|
5
|
-
.panel-body
|
6
|
-
%p Includes features
|
1
|
+
.card-header.text-center
|
2
|
+
%h3= plan[:name]
|
3
|
+
%p= plan[:description]
|
7
4
|
|
8
|
-
|
9
|
-
|
5
|
+
.card-body
|
6
|
+
%p Includes features
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
= link_to 'Select', '#', class: 'btn btn-default btn-select-plan'
|
8
|
+
- if plan[:amount] == 0
|
9
|
+
%p Free plan
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
.visible-when-unselected.text-center
|
12
|
+
= link_to 'Select', '#', class: 'btn btn-secondary', 'data-toggle': 'card'
|
13
|
+
|
14
|
+
.visible-when-selected.text-center
|
15
|
+
- if subscribed
|
16
|
+
%p
|
17
|
+
%strong Your current plan
|
18
|
+
- else
|
19
|
+
%p This is #{['an amazing', 'a great', 'an excellent', 'an exceptional', 'a marvelous', 'a wonderful'].sample} plan
|
21
20
|
|
data/config/effective_orders.rb
CHANGED
@@ -225,19 +225,19 @@ EffectiveOrders.setup do |config|
|
|
225
225
|
|
226
226
|
# if Rails.env.production?
|
227
227
|
# config.stripe = {
|
228
|
-
# secret_key: '',
|
229
|
-
# publishable_key: '',
|
228
|
+
# secret_key: 'sk_xxx',
|
229
|
+
# publishable_key: 'pk_xxx',
|
230
230
|
# currency: 'usd',
|
231
231
|
# site_title: 'My Site',
|
232
|
-
# site_image: '' # A relative URL pointing to a square image of your brand or product. The recommended minimum size is 128x128px.
|
232
|
+
# site_image: 'logo.png' # A relative or absolute URL pointing to a square image of your brand or product. The recommended minimum size is 128x128px.
|
233
233
|
# }
|
234
234
|
# else
|
235
235
|
# config.stripe = {
|
236
|
-
# secret_key: '',
|
237
|
-
# publishable_key: '',
|
236
|
+
# secret_key: 'sk_test_xxx',
|
237
|
+
# publishable_key: 'pk_test_xxx',
|
238
238
|
# currency: 'usd',
|
239
|
-
# site_title: 'My
|
240
|
-
# site_image: '' # A relative URL pointing to a square image of your brand or product. The recommended minimum size is 128x128px.
|
239
|
+
# site_title: 'My Site',
|
240
|
+
# site_image: 'logo.png' # A relative or absolute URL pointing to a square image of your brand or product. The recommended minimum size is 128x128px.
|
241
241
|
# }
|
242
242
|
# end
|
243
243
|
|
@@ -245,11 +245,17 @@ EffectiveOrders.setup do |config|
|
|
245
245
|
config.subscriptions = false
|
246
246
|
|
247
247
|
# config.subscriptions = {
|
248
|
-
#
|
249
|
-
#
|
250
|
-
|
251
|
-
#
|
252
|
-
|
248
|
+
# webhook_secret: 'whsec_xxx'
|
249
|
+
# }
|
250
|
+
|
251
|
+
# Trial
|
252
|
+
config.trial = false
|
253
|
+
|
254
|
+
# config.trial = {
|
255
|
+
# name: 'Free Trial',
|
256
|
+
# description: '45-Day Free Trial',
|
257
|
+
# length: 45.days,
|
258
|
+
# remind_at: [1.day, 3.days, 7.days], # Send email notification to trialing users 1, 3 and 7 days before expiring. false to disable.
|
253
259
|
# }
|
254
260
|
|
255
261
|
end
|
@@ -32,7 +32,7 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
|
|
32
32
|
t.string :purchasable_type
|
33
33
|
t.integer :purchasable_id
|
34
34
|
|
35
|
-
t.string :
|
35
|
+
t.string :name
|
36
36
|
t.integer :quantity
|
37
37
|
t.integer :price, :default => 0
|
38
38
|
t.boolean :tax_exempt
|
@@ -91,10 +91,7 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
|
|
91
91
|
t.string :subscribable_type
|
92
92
|
|
93
93
|
t.string :stripe_plan_id
|
94
|
-
t.string :status
|
95
|
-
|
96
94
|
t.string :name
|
97
|
-
t.integer :price, :default => 0
|
98
95
|
|
99
96
|
t.timestamps
|
100
97
|
end
|
@@ -106,7 +103,7 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
|
|
106
103
|
create_table <%= @products_table_name %> do |t|
|
107
104
|
t.integer :purchased_order_id
|
108
105
|
|
109
|
-
t.string :
|
106
|
+
t.string :name
|
110
107
|
t.integer :price
|
111
108
|
t.boolean :tax_exempt, :default => false
|
112
109
|
|
data/lib/effective_orders.rb
CHANGED
@@ -10,6 +10,12 @@ module EffectiveOrders
|
|
10
10
|
|
11
11
|
STATES = { PENDING => PENDING, CONFIRMED => CONFIRMED, PURCHASED => PURCHASED, DECLINED => DECLINED }
|
12
12
|
|
13
|
+
# Subscription statuses (as per stripe)
|
14
|
+
ACTIVE = 'active'.freeze
|
15
|
+
PAST_DUE = 'past_due'.freeze
|
16
|
+
|
17
|
+
STATUSES = { ACTIVE => ACTIVE, PAST_DUE => PAST_DUE }
|
18
|
+
|
13
19
|
# The following are all valid config keys
|
14
20
|
mattr_accessor :orders_table_name
|
15
21
|
mattr_accessor :order_items_table_name
|
@@ -57,6 +63,7 @@ module EffectiveOrders
|
|
57
63
|
mattr_accessor :paypal
|
58
64
|
mattr_accessor :stripe
|
59
65
|
mattr_accessor :subscriptions # Stripe subscriptions
|
66
|
+
mattr_accessor :trial # Trial mode
|
60
67
|
|
61
68
|
def self.setup
|
62
69
|
yield self
|
@@ -125,6 +132,10 @@ module EffectiveOrders
|
|
125
132
|
subscriptions.kind_of?(Hash)
|
126
133
|
end
|
127
134
|
|
135
|
+
def self.trial?
|
136
|
+
trial.kind_of?(Hash)
|
137
|
+
end
|
138
|
+
|
128
139
|
def self.single_payment_processor?
|
129
140
|
[cheque?, moneris?, paypal?, stripe?].select { |enabled| enabled }.length == 1
|
130
141
|
end
|
@@ -157,6 +168,12 @@ module EffectiveOrders
|
|
157
168
|
return {} unless (stripe? && subscriptions?)
|
158
169
|
|
159
170
|
@stripe_plans ||= (
|
171
|
+
Rails.logger.info "STRIPE PLAN LIST"
|
172
|
+
|
173
|
+
# {"id":"plan_ChLx90ggbWvB8i","object":"plan","amount":100000,"billing_scheme":"per_unit","created":1523984756,"currency":"usd",
|
174
|
+
#{ }"interval":"year","interval_count":1,"livemode":false,"metadata":{},"nickname":"Yearly","product":"prod_ChLwW0XqdykIki",
|
175
|
+
#{ }"tiers":null,"tiers_mode":null,"transform_usage":null,"trial_period_days":null,"usage_type":"licensed"},
|
176
|
+
|
160
177
|
plans = Stripe::Plan.all.inject({}) do |h, plan|
|
161
178
|
occurrence = case plan.interval
|
162
179
|
when 'daily' ; '/day'
|
@@ -172,7 +189,8 @@ module EffectiveOrders
|
|
172
189
|
|
173
190
|
h[plan.id] = {
|
174
191
|
id: plan.id,
|
175
|
-
|
192
|
+
product_id: plan.product,
|
193
|
+
name: plan.nickname,
|
176
194
|
amount: plan.amount,
|
177
195
|
currency: plan.currency,
|
178
196
|
description: "$#{'%0.2f' % (plan.amount / 100.0)} #{plan.currency.upcase}#{occurrence}",
|
@@ -182,12 +200,9 @@ module EffectiveOrders
|
|
182
200
|
}; h
|
183
201
|
end
|
184
202
|
|
185
|
-
|
186
|
-
id: 'trial',
|
187
|
-
|
188
|
-
name: (subscriptions[:trial_name] || 'Free Trial'),
|
189
|
-
description: (subscriptions[:trial_description] || 'Free Trial')
|
190
|
-
}
|
203
|
+
if trial?
|
204
|
+
plans['trial'] = { id: 'trial', amount: 0, name: trial.fetch(:name), description: trial.fetch(:description) }
|
205
|
+
end
|
191
206
|
|
192
207
|
plans
|
193
208
|
)
|
@@ -68,10 +68,10 @@ class EffectiveOrdersMailerPreview < ActionMailer::Preview
|
|
68
68
|
# so that this mailer will not have to guess at your app's acts_as_purchasable objects
|
69
69
|
def preview_order_items
|
70
70
|
[
|
71
|
-
{
|
72
|
-
{
|
73
|
-
{
|
74
|
-
{
|
71
|
+
{ name: 'Item One', quantity: 2, price: 999, tax_exempt: false },
|
72
|
+
{ name: 'Item Two', quantity: 1, price: 25000, tax_exempt: false },
|
73
|
+
{ name: 'Item Three', quantity: 1, price: 8999, tax_exempt: false },
|
74
|
+
{ name: 'Item Four', quantity: 1, price: 100000, tax_exempt: false }
|
75
75
|
]
|
76
76
|
end
|
77
77
|
|
@@ -1,28 +1,28 @@
|
|
1
|
-
# rake effective_orders:
|
1
|
+
# rake effective_orders:overwrite_order_item_names
|
2
2
|
|
3
3
|
namespace :effective_orders do
|
4
|
-
desc 'Overwrite all order item
|
5
|
-
task
|
6
|
-
puts 'WARNING: this task will overwrite all existing order items with new
|
4
|
+
desc 'Overwrite all order item names with current acts_as_purchasable object names'
|
5
|
+
task overwrite_order_item_names: :environment do
|
6
|
+
puts 'WARNING: this task will overwrite all existing order items with new names. Proceed? (y/n)'
|
7
7
|
(puts 'Aborted' and exit) unless STDIN.gets.chomp.downcase == 'y'
|
8
8
|
|
9
9
|
Effective::OrderItem.transaction do
|
10
10
|
begin
|
11
11
|
|
12
12
|
Effective::OrderItem.includes(:purchasable).find_each do |order_item|
|
13
|
-
|
13
|
+
new_name = order_item.purchasable.purchasable_name
|
14
14
|
|
15
|
-
unless
|
15
|
+
unless new_name
|
16
16
|
raise "acts_as_purchasable object #{order_item.purchasable_type.try(:classify)}<#{order_item.purchasable_id}>.title() from Effective::OrderItem<#{order_item.id}> cannot be nil."
|
17
17
|
end
|
18
18
|
|
19
|
-
order_item.update_column(:
|
19
|
+
order_item.update_column(:name, new_name) # This intentionally skips validation
|
20
20
|
end
|
21
21
|
|
22
|
-
puts 'Successfully updated all order item
|
22
|
+
puts 'Successfully updated all order item names.'
|
23
23
|
rescue => e
|
24
24
|
puts "An error has occurred: #{e.message}"
|
25
|
-
puts "(effective_orders) Rollback. No order item
|
25
|
+
puts "(effective_orders) Rollback. No order item names have been changed."
|
26
26
|
raise ActiveRecord::Rollback
|
27
27
|
end
|
28
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effective_orders
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.
|
4
|
+
version: 4.0.0beta5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Code and Effect
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -125,7 +125,6 @@ files:
|
|
125
125
|
- app/assets/stylesheets/effective_orders.scss
|
126
126
|
- app/assets/stylesheets/effective_orders/_cart.scss
|
127
127
|
- app/assets/stylesheets/effective_orders/_order.scss
|
128
|
-
- app/assets/stylesheets/effective_orders/_subscriptions.scss
|
129
128
|
- app/controllers/admin/customers_controller.rb
|
130
129
|
- app/controllers/admin/order_items_controller.rb
|
131
130
|
- app/controllers/admin/orders_controller.rb
|
@@ -1,14 +0,0 @@
|
|
1
|
-
// This is the radio button form input
|
2
|
-
.effective-orders-stripe-plans {
|
3
|
-
input { display: none; }
|
4
|
-
label { width: 100%; padding-left: 0px; }
|
5
|
-
.radio + .radio { margin-top: 10px; }
|
6
|
-
|
7
|
-
.visible-when-selected { display: none; }
|
8
|
-
.visible-when-unselected { display: block; }
|
9
|
-
|
10
|
-
label > .selected {
|
11
|
-
.visible-when-selected { display: block; }
|
12
|
-
.visible-when-unselected { display: none; }
|
13
|
-
}
|
14
|
-
}
|