effective_orders 4.0.0beta4 → 4.0.0beta5
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.
- 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
|
-
}
|