effective_orders 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +856 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/images/effective_orders/stripe_connect.png +0 -0
  6. data/app/assets/javascripts/effective_orders/shipping_address_toggle.js.coffee +30 -0
  7. data/app/assets/javascripts/effective_orders/stripe_charges.js.coffee +26 -0
  8. data/app/assets/javascripts/effective_orders/stripe_subscriptions.js.coffee +28 -0
  9. data/app/assets/javascripts/effective_orders.js +2 -0
  10. data/app/assets/stylesheets/effective_orders/_order.scss +30 -0
  11. data/app/assets/stylesheets/effective_orders.css.scss +1 -0
  12. data/app/controllers/admin/customers_controller.rb +15 -0
  13. data/app/controllers/admin/orders_controller.rb +22 -0
  14. data/app/controllers/effective/carts_controller.rb +70 -0
  15. data/app/controllers/effective/orders_controller.rb +191 -0
  16. data/app/controllers/effective/providers/moneris.rb +94 -0
  17. data/app/controllers/effective/providers/paypal.rb +29 -0
  18. data/app/controllers/effective/providers/stripe.rb +125 -0
  19. data/app/controllers/effective/providers/stripe_connect.rb +47 -0
  20. data/app/controllers/effective/subscriptions_controller.rb +123 -0
  21. data/app/controllers/effective/webhooks_controller.rb +86 -0
  22. data/app/helpers/effective_carts_helper.rb +90 -0
  23. data/app/helpers/effective_orders_helper.rb +108 -0
  24. data/app/helpers/effective_paypal_helper.rb +37 -0
  25. data/app/helpers/effective_stripe_helper.rb +63 -0
  26. data/app/mailers/effective/orders_mailer.rb +64 -0
  27. data/app/models/concerns/acts_as_purchasable.rb +134 -0
  28. data/app/models/effective/access_denied.rb +17 -0
  29. data/app/models/effective/cart.rb +65 -0
  30. data/app/models/effective/cart_item.rb +40 -0
  31. data/app/models/effective/customer.rb +61 -0
  32. data/app/models/effective/datatables/customers.rb +45 -0
  33. data/app/models/effective/datatables/orders.rb +53 -0
  34. data/app/models/effective/order.rb +247 -0
  35. data/app/models/effective/order_item.rb +69 -0
  36. data/app/models/effective/stripe_charge.rb +35 -0
  37. data/app/models/effective/subscription.rb +95 -0
  38. data/app/models/inputs/price_field.rb +63 -0
  39. data/app/models/inputs/price_form_input.rb +7 -0
  40. data/app/models/inputs/price_formtastic_input.rb +9 -0
  41. data/app/models/inputs/price_input.rb +19 -0
  42. data/app/models/inputs/price_simple_form_input.rb +8 -0
  43. data/app/models/validators/effective/sold_out_validator.rb +7 -0
  44. data/app/views/active_admin/effective_orders/orders/_show.html.haml +70 -0
  45. data/app/views/admin/customers/_actions.html.haml +2 -0
  46. data/app/views/admin/customers/index.html.haml +10 -0
  47. data/app/views/admin/orders/index.html.haml +7 -0
  48. data/app/views/admin/orders/show.html.haml +11 -0
  49. data/app/views/effective/carts/_cart.html.haml +33 -0
  50. data/app/views/effective/carts/show.html.haml +18 -0
  51. data/app/views/effective/orders/_checkout_step_1.html.haml +39 -0
  52. data/app/views/effective/orders/_checkout_step_2.html.haml +18 -0
  53. data/app/views/effective/orders/_my_purchases.html.haml +15 -0
  54. data/app/views/effective/orders/_order.html.haml +4 -0
  55. data/app/views/effective/orders/_order_header.html.haml +21 -0
  56. data/app/views/effective/orders/_order_items.html.haml +39 -0
  57. data/app/views/effective/orders/_order_payment_details.html.haml +11 -0
  58. data/app/views/effective/orders/_order_shipping.html.haml +19 -0
  59. data/app/views/effective/orders/_order_user_fields.html.haml +10 -0
  60. data/app/views/effective/orders/checkout.html.haml +3 -0
  61. data/app/views/effective/orders/declined.html.haml +10 -0
  62. data/app/views/effective/orders/moneris/_form.html.haml +34 -0
  63. data/app/views/effective/orders/my_purchases.html.haml +6 -0
  64. data/app/views/effective/orders/my_sales.html.haml +28 -0
  65. data/app/views/effective/orders/new.html.haml +4 -0
  66. data/app/views/effective/orders/paypal/_form.html.haml +5 -0
  67. data/app/views/effective/orders/purchased.html.haml +10 -0
  68. data/app/views/effective/orders/show.html.haml +17 -0
  69. data/app/views/effective/orders/stripe/_form.html.haml +8 -0
  70. data/app/views/effective/orders/stripe/_subscription_fields.html.haml +7 -0
  71. data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +8 -0
  72. data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +8 -0
  73. data/app/views/effective/orders_mailer/order_receipt_to_seller.html.haml +30 -0
  74. data/app/views/effective/subscriptions/index.html.haml +16 -0
  75. data/app/views/effective/subscriptions/new.html.haml +10 -0
  76. data/app/views/effective/subscriptions/show.html.haml +49 -0
  77. data/config/routes.rb +57 -0
  78. data/db/migrate/01_create_effective_orders.rb.erb +91 -0
  79. data/db/upgrade/02_upgrade_effective_orders_from03x.rb.erb +29 -0
  80. data/db/upgrade/upgrade_price_column_on_table.rb.erb +17 -0
  81. data/lib/effective_orders/engine.rb +52 -0
  82. data/lib/effective_orders/version.rb +3 -0
  83. data/lib/effective_orders.rb +76 -0
  84. data/lib/generators/effective_orders/install_generator.rb +38 -0
  85. data/lib/generators/effective_orders/upgrade_from03x_generator.rb +34 -0
  86. data/lib/generators/effective_orders/upgrade_price_column_generator.rb +34 -0
  87. data/lib/generators/templates/README +1 -0
  88. data/lib/generators/templates/effective_orders.rb +210 -0
  89. data/spec/controllers/carts_controller_spec.rb +143 -0
  90. data/spec/controllers/moneris_orders_controller_spec.rb +245 -0
  91. data/spec/controllers/orders_controller_spec.rb +418 -0
  92. data/spec/controllers/stripe_orders_controller_spec.rb +127 -0
  93. data/spec/controllers/webhooks_controller_spec.rb +79 -0
  94. data/spec/dummy/README.rdoc +8 -0
  95. data/spec/dummy/Rakefile +6 -0
  96. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  97. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  98. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  99. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  100. data/spec/dummy/app/models/product.rb +17 -0
  101. data/spec/dummy/app/models/product_with_float_price.rb +17 -0
  102. data/spec/dummy/app/models/user.rb +28 -0
  103. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  104. data/spec/dummy/bin/bundle +3 -0
  105. data/spec/dummy/bin/rails +4 -0
  106. data/spec/dummy/bin/rake +4 -0
  107. data/spec/dummy/config/application.rb +31 -0
  108. data/spec/dummy/config/boot.rb +5 -0
  109. data/spec/dummy/config/database.yml +25 -0
  110. data/spec/dummy/config/environment.rb +5 -0
  111. data/spec/dummy/config/environments/development.rb +37 -0
  112. data/spec/dummy/config/environments/production.rb +83 -0
  113. data/spec/dummy/config/environments/test.rb +39 -0
  114. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  115. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  116. data/spec/dummy/config/initializers/devise.rb +254 -0
  117. data/spec/dummy/config/initializers/effective_addresses.rb +15 -0
  118. data/spec/dummy/config/initializers/effective_orders.rb +22 -0
  119. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  120. data/spec/dummy/config/initializers/inflections.rb +16 -0
  121. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  122. data/spec/dummy/config/initializers/session_store.rb +3 -0
  123. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  124. data/spec/dummy/config/locales/en.yml +23 -0
  125. data/spec/dummy/config/routes.rb +3 -0
  126. data/spec/dummy/config/secrets.yml +22 -0
  127. data/spec/dummy/config.ru +4 -0
  128. data/spec/dummy/db/schema.rb +142 -0
  129. data/spec/dummy/db/test.sqlite3 +0 -0
  130. data/spec/dummy/log/development.log +487 -0
  131. data/spec/dummy/log/test.log +347 -0
  132. data/spec/dummy/public/404.html +67 -0
  133. data/spec/dummy/public/422.html +67 -0
  134. data/spec/dummy/public/500.html +66 -0
  135. data/spec/dummy/public/favicon.ico +0 -0
  136. data/spec/helpers/effective_orders_helper_spec.rb +21 -0
  137. data/spec/models/acts_as_purchasable_spec.rb +107 -0
  138. data/spec/models/customer_spec.rb +71 -0
  139. data/spec/models/factories_spec.rb +13 -0
  140. data/spec/models/order_item_spec.rb +35 -0
  141. data/spec/models/order_spec.rb +323 -0
  142. data/spec/models/stripe_charge_spec.rb +39 -0
  143. data/spec/models/subscription_spec.rb +103 -0
  144. data/spec/spec_helper.rb +44 -0
  145. data/spec/support/factories.rb +118 -0
  146. metadata +387 -0
@@ -0,0 +1,125 @@
1
+ require 'stripe'
2
+
3
+ module Effective
4
+ module Providers
5
+ module Stripe
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ end
10
+
11
+ def stripe_charge
12
+ @order = Effective::Order.find(stripe_charge_params[:effective_order_id])
13
+ @stripe_charge = Effective::StripeCharge.new(stripe_charge_params)
14
+ @stripe_charge.order = @order
15
+
16
+ EffectiveOrders.authorized?(self, :update, @order)
17
+
18
+ if @stripe_charge.valid? && (response = process_stripe_charge(@stripe_charge)) != false
19
+ order_purchased(response) # orders_controller#order_purchased
20
+ else
21
+ flash[:danger] = @stripe_charge.errors.full_messages.join(',')
22
+ render 'checkout'
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def process_stripe_charge(charge)
29
+ Effective::Order.transaction do
30
+ begin
31
+ @buyer = Customer.for_user(charge.order.user)
32
+ @buyer.update_card!(charge.token)
33
+
34
+ if EffectiveOrders.stripe_connect_enabled
35
+ return charge_with_stripe_connect(charge, @buyer)
36
+ else
37
+ return charge_with_stripe(charge, @buyer)
38
+ end
39
+ rescue => e
40
+ charge.errors.add(:base, "Unable to process order with Stripe. Your credit card has not been charged. Message: \"#{e.message}\".")
41
+ raise ActiveRecord::Rollback
42
+ end
43
+ end
44
+
45
+ false
46
+ end
47
+
48
+ def charge_with_stripe(charge, buyer)
49
+ results = {:subscriptions => {}, :charge => nil}
50
+
51
+ # Process subscriptions.
52
+ charge.subscriptions.each do |subscription|
53
+ next if subscription.stripe_plan_id.blank?
54
+
55
+ stripe_subscription = if subscription.stripe_coupon_id.present?
56
+ buyer.stripe_customer.subscriptions.create({:plan => subscription.stripe_plan_id, :coupon => subscription.stripe_coupon_id})
57
+ else
58
+ buyer.stripe_customer.subscriptions.create({:plan => subscription.stripe_plan.id})
59
+ end
60
+
61
+ subscription.stripe_subscription_id = stripe_subscription.id
62
+ subscription.save!
63
+
64
+ results[:subscriptions][subscription.stripe_plan_id] = JSON.parse(stripe_subscription.to_json)
65
+ end
66
+
67
+ # Process regular order_items.
68
+ amount = (charge.order_items.collect(&:total).sum) # A positive integer in cents representing how much to charge the card. The minimum amount is 50 cents.
69
+ description = "Charge for Order ##{charge.order.to_param}"
70
+
71
+ if amount > 0
72
+ results[:charge] = JSON.parse(::Stripe::Charge.create(
73
+ :amount => amount,
74
+ :currency => EffectiveOrders.stripe[:currency],
75
+ :customer => buyer.stripe_customer.id,
76
+ :description => description
77
+ ).to_json)
78
+ end
79
+
80
+ results
81
+ end
82
+
83
+ def charge_with_stripe_connect(charge, buyer)
84
+ # Go through and create Stripe::Tokens for each seller
85
+ items = charge.order_items.group_by(&:seller)
86
+ results = {}
87
+
88
+ # We do all these Tokens first, so if one throws an exception no charges are made
89
+ items.each do |seller, _|
90
+ seller.token = ::Stripe::Token.create({:customer => buyer.stripe_customer.id}, seller.stripe_connect_access_token)
91
+ end
92
+
93
+ # Make one charge per seller, for all his order_items
94
+ items.each do |seller, order_items|
95
+ amount = order_items.sum(&:total)
96
+ description = "Charge for Order ##{charge.order.to_param} with OrderItems ##{order_items.map(&:id).join(', #')}"
97
+ application_fee = order_items.sum(&:stripe_connect_application_fee)
98
+
99
+ results[seller.id] = JSON.parse(::Stripe::Charge.create(
100
+ {
101
+ :amount => amount,
102
+ :currency => EffectiveOrders.stripe[:currency],
103
+ :card => seller.token.id,
104
+ :description => description,
105
+ :application_fee => application_fee
106
+ },
107
+ seller.stripe_connect_access_token
108
+ ).to_json)
109
+ end
110
+
111
+ results
112
+ end
113
+
114
+ # StrongParameters
115
+ def stripe_charge_params
116
+ begin
117
+ params.require(:effective_stripe_charge).permit(:token, :effective_order_id)
118
+ rescue => e
119
+ params[:effective_stripe_charge]
120
+ end
121
+ end
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,47 @@
1
+ module Effective
2
+ module Providers
3
+ module StripeConnect
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ prepend_before_filter :set_stripe_connect_state_params, :only => [:stripe_connect_redirect_uri]
8
+ end
9
+
10
+ # So this is the postback after Stripe does its oAuth authentication
11
+ def stripe_connect_redirect_uri
12
+ if params[:code].present?
13
+ token_params = request_access_token(params[:code]) # We got a code, so now we make a curl request for the access_token
14
+ customer = Effective::Customer.for_user(current_user)
15
+
16
+ if token_params['access_token'].present? && customer.present?
17
+ if customer.update_attributes(:stripe_connect_access_token => token_params['access_token'])
18
+ flash[:success] = 'Successfully Connected with Stripe Connect'
19
+ else
20
+ flash[:danger] = "Unable to update customer: #{customer.errors[:base].first}"
21
+ end
22
+ else
23
+ flash[:danger] = "Error when connecting to Stripe /oauth/token: #{token_params[:error]}. Please try again."
24
+ end
25
+ else
26
+ flash[:danger] = "Error when connecting to Stripe /oauth/authorize: #{params[:error]}. Please try again."
27
+ end
28
+
29
+ redirect_to URI.parse(@stripe_state_params['redirect_to']).path rescue effective_orders.orders_path
30
+ end
31
+
32
+ private
33
+
34
+ def request_access_token(code)
35
+ stripe_response = `curl -F client_secret='#{EffectiveOrders.stripe[:secret_key]}' -F code='#{code}' -F grant_type='authorization_code' #{EffectiveStripeHelper::STRIPE_CONNECT_TOKEN_URL}`
36
+ JSON.parse(stripe_response) rescue {}
37
+ end
38
+
39
+ def set_stripe_connect_state_params
40
+ @stripe_state_params = (JSON.parse(params[:state]) rescue {})
41
+ @stripe_state_params = {} unless @stripe_state_params.kind_of?(Hash)
42
+
43
+ params[:authenticity_token] = @stripe_state_params['form_authenticity_token']
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,123 @@
1
+ module Effective
2
+ class SubscriptionsController < ApplicationController
3
+ include EffectiveCartsHelper
4
+ include EffectiveStripeHelper
5
+
6
+ layout (EffectiveOrders.layout.kind_of?(Hash) ? EffectiveOrders.layout[:subscriptions] : EffectiveOrders.layout)
7
+
8
+ before_filter :authenticate_user!
9
+ before_filter :assign_customer
10
+
11
+ # This is a 'My Subscriptions' page
12
+ def index
13
+ @page_title ||= 'My Subscriptions'
14
+
15
+ @subscriptions = @customer.subscriptions.purchased
16
+
17
+ EffectiveOrders.authorized?(self, :index, Effective::Subscription)
18
+ end
19
+
20
+ def new
21
+ @page_title ||= 'New Subscription'
22
+
23
+ @subscription = @customer.subscriptions.new()
24
+
25
+ purchased_plans = @customer.subscriptions.purchased.map(&:stripe_plan_id)
26
+ @plans = Stripe::Plan.all.reject { |stripe_plan| purchased_plans.include?(stripe_plan.id) }
27
+
28
+ EffectiveOrders.authorized?(self, :new, @subscription)
29
+ end
30
+
31
+ def create
32
+ @page_title ||= 'New Subscription'
33
+
34
+ # Don't let the user create another Subscription object if it's already created
35
+ @subscription = @customer.subscriptions.where(:stripe_plan_id => subscription_params[:stripe_plan_id]).first_or_initialize
36
+
37
+ EffectiveOrders.authorized?(self, :create, @subscription)
38
+
39
+ if @subscription.update_attributes(subscription_params) && (current_cart.find(@subscription).present? || current_cart.add(@subscription))
40
+ flash[:success] = "Successfully added subscription to cart"
41
+ redirect_to effective_orders.new_order_path
42
+ else
43
+ purchased_plans = @customer.subscriptions.purchased.map(&:stripe_plan_id)
44
+ @plans = Stripe::Plan.all.reject { |stripe_plan| purchased_plans.include?(stripe_plan.id) }
45
+
46
+ flash[:danger] ||= 'Unable to add subscription to cart. Please try again.'
47
+ render :action => :new
48
+ end
49
+ end
50
+
51
+ def show
52
+ @plan = Stripe::Plan.retrieve(params[:id])
53
+
54
+ unless @plan.present?
55
+ flash[:danger] = "Unrecognized Stripe Plan: #{params[:id]}"
56
+ raise ActiveRecord::RecordNotFound
57
+ end
58
+
59
+ @subscription = @customer.subscriptions.find { |subscription| subscription.stripe_plan_id == params[:id] }
60
+
61
+ unless @subscription.present?
62
+ flash[:danger] = "Unable to find Customer Subscription for plan: #{params[:id]}"
63
+ raise ActiveRecord::RecordNotFound
64
+ end
65
+
66
+ @stripe_subscription = @subscription.try(:stripe_subscription)
67
+
68
+ unless @stripe_subscription.present?
69
+ flash[:danger] = "Unable to find Stripe Subscription for plan: #{params[:id]}"
70
+ raise ActiveRecord::RecordNotFound
71
+ end
72
+
73
+ EffectiveOrders.authorized?(self, :show, @subscription)
74
+
75
+ @invoices = @customer.stripe_customer.invoices.all.select do |invoice|
76
+ invoice.lines.any? { |line| line.id == @stripe_subscription.id }
77
+ end
78
+
79
+ @page_title ||= "#{@plan.name}"
80
+ end
81
+
82
+ def destroy
83
+ @plan = Stripe::Plan.retrieve(params[:id])
84
+ raise ActiveRecord::RecordNotFound unless @plan.present?
85
+
86
+ @subscription = @customer.subscriptions.find { |subscription| subscription.stripe_plan_id == params[:id] }
87
+ @stripe_subscription = @subscription.try(:stripe_subscription)
88
+ raise ActiveRecord::RecordNotFound unless @subscription.present?
89
+
90
+ EffectiveOrders.authorized?(self, :destroy, @subscription)
91
+
92
+ if @subscription.present?
93
+ begin
94
+ @stripe_subscription.delete if @stripe_subscription
95
+ @subscription.destroy
96
+ flash[:success] = "Successfully unsubscribed from #{params[:id]}"
97
+ rescue => e
98
+ flash[:danger] = "Unable to unsubscribe. Message: \"#{e.message}\"."
99
+ end
100
+ else
101
+ flash[:danger] = "Unable to find stripe subscription for #{params[:id]}" unless @subscription.present?
102
+ end
103
+
104
+ redirect_to effective_orders.subscriptions_path
105
+ end
106
+
107
+ private
108
+
109
+ def assign_customer
110
+ @customer ||= Customer.for_user(current_user)
111
+ end
112
+
113
+ # StrongParameters
114
+ def subscription_params
115
+ begin
116
+ params.require(:effective_subscription).permit(:stripe_plan_id, :stripe_coupon_id)
117
+ rescue => e
118
+ params[:effective_subscription]
119
+ end
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,86 @@
1
+ module Effective
2
+ class WebhooksController < ApplicationController
3
+ protect_from_forgery :except => [:stripe]
4
+ skip_authorization_check if defined?(CanCan)
5
+
6
+ # Webhook from stripe
7
+ def stripe
8
+ (head(:ok) and return) if (params[:livemode] == false && Rails.env.production?) || params[:object] != 'event' || params[:id].blank?
9
+
10
+ # Dont trust the POST, and instead request the actual event from Stripe
11
+ @event = Stripe::Event.retrieve(params[:id]) rescue (head(:ok) and return)
12
+
13
+ Effective::Customer.transaction do
14
+ begin
15
+ case @event.type
16
+ when 'customer.created' ; stripe_customer_created(@event)
17
+ when 'customer.deleted' ; stripe_customer_deleted(@event)
18
+ when 'customer.subscription.created' ; stripe_subscription_created(@event)
19
+ when 'customer.subscription.deleted' ; stripe_subscription_deleted(@event)
20
+ end
21
+ rescue => e
22
+ Rails.logger.info "Stripe Webhook Error: #{e.message}"
23
+ raise ActiveRecord::Rollback
24
+ end
25
+ end
26
+
27
+ head :ok # Always return success
28
+ end
29
+
30
+ private
31
+
32
+ def stripe_customer_created(event)
33
+ stripe_customer = event.data.object
34
+ user = ::User.where(:email => stripe_customer.email).first
35
+
36
+ if user.present?
37
+ customer = Effective::Customer.for_user(user) # This is a first_or_create
38
+ customer.stripe_customer_id = stripe_customer.id
39
+ customer.save!
40
+ end
41
+ end
42
+
43
+ def stripe_customer_deleted(event)
44
+ stripe_customer = event.data.object
45
+ user = ::User.where(:email => stripe_customer.email).first
46
+
47
+ if user.present?
48
+ customer = Effective::Customer.where(:user_id => user.id).first
49
+ customer.destroy! if customer
50
+ end
51
+ end
52
+
53
+ def stripe_subscription_created(event)
54
+ stripe_subscription = event.data.object
55
+ @customer = Effective::Customer.where(:stripe_customer_id => stripe_subscription.customer).first
56
+
57
+ if @customer.present?
58
+ subscription = @customer.subscriptions.where(:stripe_plan_id => stripe_subscription.plan.id).first_or_initialize
59
+
60
+ subscription.stripe_subscription_id = stripe_subscription.id
61
+ subscription.stripe_plan_id = (stripe_subscription.plan.id rescue nil)
62
+ subscription.stripe_coupon_id = stripe_subscription.discount.coupon.id if (stripe_subscription.discount.present? rescue false)
63
+
64
+ subscription.save!
65
+
66
+ unless subscription.purchased?
67
+ # Now we have to purchase it
68
+ @order = Effective::Order.new(subscription)
69
+ @order.user = @customer.user
70
+ @order.purchase!("via Stripe webhook #{event.id}")
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ def stripe_subscription_deleted(event)
77
+ stripe_subscription = event.data.object
78
+ customer = Effective::Customer.where(:stripe_customer_id => stripe_subscription.customer).first
79
+
80
+ if customer.present?
81
+ customer.subscriptions.find { |subscription| subscription.stripe_plan_id == stripe_subscription.plan.id }.try(:destroy)
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,90 @@
1
+ module EffectiveCartsHelper
2
+ def current_cart(for_user = nil)
3
+ @cart ||= (
4
+ user = for_user || (current_user rescue nil) # rescue protects me against Devise not being installed
5
+
6
+ if user.present?
7
+ Effective::Cart.where(:user_id => user.id).first_or_create.tap do |user_cart|
8
+ if session[:cart].present?
9
+ session_cart = Effective::Cart.where('user_id IS NULL').where(:id => session[:cart]).first
10
+
11
+ if session_cart.present?
12
+ session_cart.cart_items.update_all(:cart_id => user_cart.id)
13
+ session_cart.destroy
14
+ user_cart.reload
15
+ end
16
+
17
+ session[:cart] = nil
18
+ end
19
+ end
20
+ elsif session[:cart].present?
21
+ Effective::Cart.where('user_id IS NULL').where(:id => session[:cart]).first_or_create
22
+ else
23
+ cart = Effective::Cart.create!
24
+ session[:cart] = cart.id
25
+ cart
26
+ end
27
+ )
28
+ end
29
+
30
+ def link_to_current_cart(opts = {})
31
+ options = {:id => 'current_cart', :rel => :nofollow}.merge(opts)
32
+
33
+ if current_cart.size == 0
34
+ link_to (options.delete(:label) || 'Cart'), effective_orders.cart_path, options
35
+ else
36
+ link_to (options.delete(:label) || "Cart (#{current_cart.size})"), effective_orders.cart_path, options
37
+ end
38
+ end
39
+
40
+ def link_to_add_to_cart(purchasable, opts = {})
41
+ raise ArgumentError.new('expecting an acts_as_purchasable object') unless purchasable.respond_to?(:is_effectively_purchasable?)
42
+
43
+ options = {:class => 'btn', :rel => :nofollow, 'data-disable-with' => 'Add to Cart...'}.merge(opts)
44
+ options[:class] = ((options[:class] || '') + ' btn-add-to-cart')
45
+
46
+ link_to (options.delete(:label) || 'Add to Cart'), effective_orders.add_to_cart_path(:purchasable_type => purchasable.class.name, :purchasable_id => purchasable.id.to_i), options
47
+ end
48
+
49
+ def link_to_remove_from_cart(cart_item, opts = {})
50
+ raise ArgumentError.new('expecting an Effective::CartItem object') unless cart_item.kind_of?(Effective::CartItem)
51
+
52
+ options = {
53
+ :rel => :nofollow,
54
+ :data => {:confirm => 'Are you sure? This cannot be undone!'},
55
+ :method => :delete
56
+ }.merge(opts)
57
+ options[:class] = ((options[:class] || '') + ' btn-remove-from-cart')
58
+
59
+ link_to (options.delete(:label) || 'Remove'), effective_orders.remove_from_cart_path(cart_item), options
60
+ end
61
+
62
+ def link_to_empty_cart(opts = {})
63
+ options = {
64
+ :rel => :nofollow,
65
+ :class => 'btn',
66
+ :data => {:confirm => 'This will clear your entire cart. Are you sure? This cannot be undone!'},
67
+ :method => :delete
68
+ }.merge(opts)
69
+ options[:class] = ((options[:class] || '') + ' btn-empty-cart btn-danger')
70
+
71
+ link_to (options.delete(:label) || 'Empty Cart'), effective_orders.cart_path, options
72
+ end
73
+
74
+ def link_to_checkout(opts = {})
75
+ options = {:class => 'btn', :rel => :nofollow}.merge(opts)
76
+ options[:class] = ((options[:class] || '') + ' btn-checkout')
77
+
78
+ link_to (options.delete(:label) || 'Proceed to Checkout'), effective_orders.new_order_path, options
79
+ end
80
+
81
+ def render_cart(cart = nil)
82
+ cart ||= current_cart
83
+ render(:partial => 'effective/carts/cart', :locals => {:cart => cart})
84
+ end
85
+
86
+ def render_purchasables(*purchasables)
87
+ render(:partial => 'effective/orders/order_items', :locals => {:order => Effective::Order.new(purchasables)})
88
+ end
89
+
90
+ end
@@ -0,0 +1,108 @@
1
+ module EffectiveOrdersHelper
2
+ def price_to_currency(price)
3
+ raise 'price_to_currency expects an Integer representing the number of cents in a price' unless price.kind_of?(Integer)
4
+ number_to_currency(price / 100.0)
5
+ end
6
+
7
+ def order_summary(order)
8
+ content_tag(:p, "#{price_to_currency(order.total)} total for #{pluralize(order.num_items, 'item')}:") +
9
+
10
+ content_tag(:ul) do
11
+ order.order_items.map do |item|
12
+ content_tag(:li) do
13
+ title = item.title.split('<br>')
14
+ "#{item.quantity}x #{title.first} for #{price_to_currency(item.price)}".tap do |output|
15
+ title[1..-1].each { |line| output << "<br>#{line}" }
16
+ end.html_safe
17
+ end
18
+ end.join().html_safe
19
+ end
20
+ end
21
+
22
+ def order_item_summary(order_item)
23
+ if order_item.quantity > 1
24
+ content_tag(:p, "#{price_to_currency(order_item.total)} total for #{pluralize(order_item.quantity, 'item')}")
25
+ else
26
+ content_tag(:p, "#{price_to_currency(order_item.total)} total")
27
+ end
28
+ end
29
+
30
+ # This is called on the My Sales Page and is intended to be overridden in the app if needed
31
+ def acts_as_purchasable_path(purchasable, action = :show)
32
+ polymorphic_path(purchasable)
33
+ end
34
+
35
+ def order_payment_to_html(order)
36
+ payment = order.payment
37
+
38
+ if order.purchased?(:stripe_connect) && order.payment.kind_of?(Hash)
39
+ payment = Hash[
40
+ order.payment.map do |seller_id, v|
41
+ if (user = Effective::Customer.find(seller_id).try(:user))
42
+ [link_to(user, admin_user_path(user)), order.payment[seller_id]]
43
+ else
44
+ [seller_id, order.payment[seller_id]]
45
+ end
46
+ end
47
+ ]
48
+ end
49
+
50
+ content_tag(:pre) do
51
+ raw JSON.pretty_generate(payment).html_safe
52
+ .gsub('\"', '')
53
+ .gsub("[\n\n ]", '[]')
54
+ .gsub("{\n }", '{}')
55
+ end
56
+ end
57
+
58
+ def render_order(order)
59
+ render(:partial => 'effective/orders/order', :locals => {:order => order})
60
+ end
61
+
62
+ def render_checkout(order, opts = {})
63
+ raise ArgumentError.new('unable to checkout an order without a user') unless order.user.present?
64
+
65
+ locals = {
66
+ :purchased_redirect_url => nil,
67
+ :declined_redirect_url => nil
68
+ }.merge(opts)
69
+
70
+ if order.new_record?
71
+ render(:partial => 'effective/orders/checkout_step_1', :locals => locals.merge({:order => order}))
72
+ else
73
+ raise ArgumentError.new('unable to checkout a persisted but invalid order') unless order.valid?
74
+ render(:partial => 'effective/orders/checkout_step_2', :locals => locals.merge({:order => order}))
75
+ end
76
+ end
77
+
78
+ def link_to_my_purchases(opts = {})
79
+ options = {:rel => :nofollow}.merge(opts)
80
+ link_to (options.delete(:label) || 'My Purchases'), effective_orders.my_purchases_path, options
81
+ end
82
+ alias_method :link_to_order_history, :link_to_my_purchases
83
+
84
+ def render_order_history(user_or_orders, opts = {})
85
+ if user_or_orders.kind_of?(User)
86
+ orders = Effective::Order.purchased_by(user_or_orders)
87
+ elsif user_or_orders.respond_to?(:to_a)
88
+ begin
89
+ orders = user_or_orders.to_a.select { |order| order.purchased? }
90
+ rescue => e
91
+ raise ArgumentError.new('expecting an instance of User or an array/collection of Effective::Order objects')
92
+ end
93
+ else
94
+ raise ArgumentError.new('expecting an instance of User or an array/collection of Effective::Order objects')
95
+ end
96
+
97
+ locals = {
98
+ :orders => orders,
99
+ :order_path => effective_orders.order_path(':id') # The :id string will be replaced with the order id
100
+ }.merge(opts)
101
+
102
+ render(:partial => 'effective/orders/my_purchases', :locals => locals)
103
+ end
104
+
105
+ alias_method :render_purchases, :render_order_history
106
+ alias_method :render_my_purchases, :render_order_history
107
+
108
+ end
@@ -0,0 +1,37 @@
1
+ module EffectivePaypalHelper
2
+ # These're constants so they only get read once, not every order request
3
+ PAYPAL_CERT_PEM = (File.read(EffectiveOrders.paypal[:paypal_cert]) rescue {})
4
+ APP_CERT_PEM = (File.read(EffectiveOrders.paypal[:app_cert]) rescue {})
5
+ APP_KEY_PEM = (File.read(EffectiveOrders.paypal[:app_key]) rescue {})
6
+
7
+ def paypal_encrypted_payload(order)
8
+ raise ArgumentError.new("unable to read EffectiveOrders PayPal paypal_cert #{EffectiveOrders.paypal[:paypal_cert]}") unless PAYPAL_CERT_PEM.present?
9
+ raise ArgumentError.new("unable to read EffectiveOrders PayPal app_cert #{EffectiveOrders.paypal[:app_cert]}") unless APP_CERT_PEM.present?
10
+ raise ArgumentError.new("unable to read EffectiveOrders PayPal app_key #{EffectiveOrders.paypal[:app_key]}") unless APP_KEY_PEM.present?
11
+
12
+ values = {
13
+ :business => EffectiveOrders.paypal[:seller_email],
14
+ :custom => EffectiveOrders.paypal[:secret],
15
+ :cmd => '_cart',
16
+ :upload => 1,
17
+ :return => effective_orders.order_purchased_url(order),
18
+ :notify_url => effective_orders.paypal_postback_url,
19
+ :cert_id => EffectiveOrders.paypal[:cert_id],
20
+ :currency_code => EffectiveOrders.paypal[:currency],
21
+ :invoice => order.id + EffectiveOrders.paypal[:order_id_nudge].to_i,
22
+ :amount => (order.subtotal / 100.0).round(2),
23
+ :tax_cart => (order.tax / 100.0).round(2)
24
+ }
25
+
26
+ order.order_items.each_with_index do |item, x|
27
+ values["item_number_#{x+1}"] = x+1
28
+ values["item_name_#{x+1}"] = item.title
29
+ values["quantity_#{x+1}"] = item.quantity
30
+ values["amount_#{x+1}"] = '%.2f' % (item.price / 100.0)
31
+ values["tax_#{x+1}"] = '%.2f' % ((item.tax / 100.0) / item.quantity) # Tax for 1 of these items
32
+ end
33
+
34
+ signed = OpenSSL::PKCS7::sign(OpenSSL::X509::Certificate.new(APP_CERT_PEM), OpenSSL::PKey::RSA.new(APP_KEY_PEM, ''), values.map { |k, v| "#{k}=#{v}" }.join("\n"), [], OpenSSL::PKCS7::BINARY)
35
+ OpenSSL::PKCS7::encrypt([OpenSSL::X509::Certificate.new(PAYPAL_CERT_PEM)], signed.to_der, OpenSSL::Cipher::Cipher::new("DES3"), OpenSSL::PKCS7::BINARY).to_s.gsub("\n", "")
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ module EffectiveStripeHelper
2
+
3
+ STRIPE_CONNECT_AUTHORIZE_URL = 'https://connect.stripe.com/oauth/authorize'
4
+ STRIPE_CONNECT_TOKEN_URL = 'https://connect.stripe.com/oauth/token'
5
+
6
+ def is_stripe_connect_seller?(user)
7
+ Effective::Customer.for_user(user).try(:is_stripe_connect_seller?) == true
8
+ end
9
+
10
+ def link_to_new_stripe_connect_customer(opts = {})
11
+ client_id = EffectiveOrders.stripe[:connect_client_id]
12
+
13
+ raise ArgumentError.new('effective_orders config: stripe.connect_client_id has not been set') unless client_id.present?
14
+
15
+ authorize_params = {
16
+ response_type: :code,
17
+ client_id: client_id, # This is the Application's ClientID
18
+ scope: :read_write,
19
+ state: {
20
+ form_authenticity_token: form_authenticity_token, # Rails standard CSRF
21
+ redirect_to: URI.encode(request.original_url) # TODO: Allow this to be customized
22
+ }.to_json
23
+ }
24
+
25
+ # Add the stripe_user parameter if it's possible
26
+ stripe_user_params = opts.delete :stripe_user
27
+ authorize_params.merge!({stripe_user: stripe_user_params}) if stripe_user_params.is_a?(Hash)
28
+
29
+ authorize_url = STRIPE_CONNECT_AUTHORIZE_URL.chomp('/') + '?' + authorize_params.to_query
30
+ options = {}.merge(opts)
31
+ link_to image_tag('/assets/effective_orders/stripe_connect.png'), authorize_url, options
32
+ end
33
+
34
+ ### Subscriptions Helpers
35
+ def stripe_plans_collection(plans)
36
+ (plans || []).map { |plan| [stripe_plan_description(plan), plan.id, {'data-amount' => plan.amount}] }
37
+ end
38
+
39
+ def stripe_plan_description(plan)
40
+ occurrence = case plan.interval
41
+ when 'weekly' ; '/week'
42
+ when 'monthly' ; '/month'
43
+ when 'yearly' ; '/year'
44
+ when 'week' ; plan.interval_count == 1 ? '/week' : " every #{plan.interval_count} weeks"
45
+ when 'month' ; plan.interval_count == 1 ? '/month' : " every #{plan.interval_count} months"
46
+ when 'year' ; plan.interval_count == 1 ? '/year' : " every #{plan.interval_count} years"
47
+ else ; plan.interval
48
+ end
49
+
50
+ "#{plan.name} - #{ActionController::Base.helpers.price_to_currency(plan.amount)} #{plan.currency.upcase}#{occurrence}"
51
+ end
52
+
53
+ def stripe_coupon_description(coupon)
54
+ amount = coupon.amount_off.present? ? ActionController::Base.helpers.price_to_currency(coupon.amount_off) : "#{coupon.percent_off}%"
55
+
56
+ if coupon.duration_in_months.present?
57
+ "#{coupon.id} - #{amount} off for #{coupon.duration_in_months} months"
58
+ else
59
+ "#{coupon.id} - #{amount} off #{coupon.duration}"
60
+ end
61
+ end
62
+
63
+ end