effective_orders 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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