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
data/README.md ADDED
@@ -0,0 +1,856 @@
1
+ # Effective Orders
2
+
3
+ Carts, Orders, and collecting payment via Stripe, PayPal and Moneris.
4
+
5
+ A Rails Engine to handle the purchase workflow in a Rails 3.2.x / Rails 4 application.
6
+
7
+ Also works with Stripe Connect and Stripe Subscriptions with coupons.
8
+
9
+ Sends order receipt emails automatically.
10
+
11
+ Has Order History, My Purchases, My Sales and Admin screens.
12
+
13
+ ## Getting Started
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'effective_orders'
19
+ ```
20
+
21
+ Run the bundle command to install it:
22
+
23
+ ```console
24
+ bundle install
25
+ ```
26
+
27
+ Then run the generator:
28
+
29
+ ```ruby
30
+ rails generate effective_orders:install
31
+ ```
32
+
33
+ The generator will install an initializer which describes all configuration options and creates a database migration.
34
+
35
+ If you want to tweak the table name (to use something other than the default 'orders', 'order_items', 'carts', 'cart_items', 'customers', 'subscriptions'), manually adjust both the configuration file and the migration now.
36
+
37
+ Then migrate the database:
38
+
39
+ ```ruby
40
+ rake db:migrate
41
+ ```
42
+
43
+ Require the javascript on the asset pipeline by adding the following to your application.js:
44
+
45
+ ```ruby
46
+ //= require effective_orders
47
+ ```
48
+
49
+ Require the stylesheet on the asset pipeline by adding the following to your application.css:
50
+
51
+ ```ruby
52
+ *= require effective_orders
53
+ ```
54
+
55
+ ### Upgrading from 0.3.x
56
+
57
+ In the 0.3.x versions of this gem, prices were internally represented as Decimals
58
+
59
+ This has been changed in 0.4.x to properly be Integer columns
60
+
61
+ If you're running a 0.3.x or earlier version, please upgrade to 0.4.x with this one command:
62
+
63
+ ```ruby
64
+ rails generate effective_orders:upgrade_from03x
65
+ ```
66
+
67
+ the above command will upgrade the order_items and subscriptions tables.
68
+
69
+ If you have additional (products or whatever..) tables with a column `price` represented as a Decimal, they should also be upgraded.
70
+
71
+ To upgrade, use this generator to create a migration on table `products` with column `price`:
72
+
73
+ ```ruby
74
+ bundle exec rails generate effective_orders:upgrade_price_column products price
75
+ ```
76
+
77
+ ## High Level Overview
78
+
79
+ Your rails app creates and displays a list of `acts_as_purchsable` objects, each with a `link_to_add_to_cart(object)`.
80
+
81
+ The user clicks one or more Add to Cart links and adds some purchasables to their cart.
82
+
83
+ They then click the Checkout link from the My Cart page, or another `link_to_checkout` displayed somewhere, which takes them to `effective_orders.new_order_path` to begin checkout.
84
+
85
+ The checkout is a 2-page process:
86
+
87
+ The first page collects billing/shipping details and gives the user their final option to 'Change Items'.
88
+
89
+ After clicking 'Save and Continue', the user will be on the collect money page.
90
+
91
+ If the payment processor is PayPal or Moneris, the user will be sent to their website to enter their credit card details.
92
+
93
+ If the payment processor is Stripe, there is an on-screen popup form to collect those details.
94
+
95
+ Once the user has successfully paid, they are redirected to a thank you page displaying the order receipt.
96
+
97
+ An email notification containing the receipt is also sent to the buyer's email address, and the site admin.
98
+
99
+
100
+ ## Usage
101
+
102
+ effective_orders handles the add_to_cart -> checkout -> collect of payment workflow, but relies on the base application to define, create and display the purchaseable things.
103
+
104
+ These purchasables could be Products, EventTickets, Memberships or anything else.
105
+
106
+
107
+ ### Representing Prices
108
+
109
+ All prices should be internally represented as Integers. For us North Americans, think of it as the number of cents.
110
+
111
+ To represent the value of `$10.00` the price method should return `1000`.
112
+
113
+ Similarly, to represent a value of `$0.50` the price method should return `50`.
114
+
115
+ EffectiveOrders does not deal with a specific currency or do any currency conversions of any kind. The main gem authors are North American, and as such this gem is unfortunately North American biased.
116
+
117
+
118
+ ### Creating a purchasable
119
+
120
+ Once installed, we still need to create something to purchase.
121
+
122
+ Let's create a `Product` model that uses the `acts_as_purchasable` mixin.
123
+
124
+ We're also going to prevent the Product from being deleted by overriding `def destroy` and instead setting a boolean `archived = true`.
125
+
126
+ If someone purchased a Product which is later deleted, the Order History page will be unable to find the Product.
127
+
128
+ ```ruby
129
+ class Product < ActiveRecord::Base
130
+ acts_as_purchasable
131
+
132
+ # this structure do... block is provided by the migrant gem https://github.com/pascalh1011/migrant
133
+ structure do
134
+ title :string
135
+
136
+ price :integer, :default => 0
137
+ tax_exempt :boolean, :default => false
138
+
139
+ archived :boolean, :default => false
140
+
141
+ timestamps
142
+ end
143
+
144
+ validates_presence_of :title
145
+ validates_numericality_of :price, :greater_than_or_equal_to => 0
146
+
147
+ scope :products, -> { where(:archived => false) }
148
+
149
+ # This archives Products instead of deleting them
150
+ def destroy
151
+ update_attributes(:archived => true)
152
+ end
153
+
154
+ end
155
+ ```
156
+
157
+ The database migration will look like the following:
158
+
159
+ ```ruby
160
+ class CreateProducts < ActiveRecord::Migration
161
+ def self.up
162
+ create_table :products do |t|
163
+ t.string :title
164
+ t.integer :price, :default=>0
165
+ t.boolean :tax_exempt, :default=>false
166
+ t.boolean :archived, :default=>false
167
+ t.datetime :updated_at
168
+ t.datetime :created_at
169
+ end
170
+ end
171
+
172
+ def self.down
173
+ drop_table :products
174
+ end
175
+ end
176
+ ```
177
+
178
+ Once the database has been migrated, it is time to scaffold/build the CRUD Product screens to create some Products to sell.
179
+
180
+ ### Products#new/#edit
181
+
182
+ Use the EffectiveOrders price input to enter the price.
183
+
184
+ It displays the underlying Integer price as a currency formatted value, ensures that a properly formatted price is entered by the user, and POSTs the appropriate Integer value back to the server.
185
+
186
+ This is available for simple_form, formtastic and Rails default FormBuilder.
187
+
188
+ ```haml
189
+ = simple_form_for(@product) do |f|
190
+ = f.input :title
191
+ = f.input :tax_exempt
192
+ = f.input :price, :as => :price
193
+ = f.button :submit
194
+ ```
195
+
196
+ or
197
+
198
+ ```ruby
199
+ = semantic_form_for(@product) do |f|
200
+ = f.input :price, :as => :price
201
+ ```
202
+
203
+ or
204
+
205
+ ```haml
206
+ = form_for(@product) do |f|
207
+ = f.price_field :price
208
+ ```
209
+
210
+ The `:as => :price` will work interchangeably with SimpleForm or Formtastic, as long as only one of these gems is present in your application
211
+
212
+ If you use both SimpleForm and Formtastic, you will need to call price input differently:
213
+
214
+ ```ruby
215
+ = simple_form_for(@product) do |f|
216
+ = f.input :price, :as => :price_simple_form
217
+
218
+ = semantic_form_for @user do |f|
219
+ = f.input :price, :as => :price_formtastic
220
+ ```
221
+
222
+ ### Products#show
223
+
224
+ So back on the Product#show page, we will render the product with an Add To Cart link
225
+
226
+ ```haml
227
+ %h4= @product.title
228
+ %p= price_to_currency(@product.price)
229
+ %p= link_to_add_to_cart(@product, :class => 'btn btn-primary', :label => 'Add To My Shopping Cart')
230
+ ```
231
+
232
+ Please take note of the `price_to_currency` helper above.
233
+
234
+ This is an EffectiveOrders helper that will display an Integer price as a currency formatted value. It does an Integer to Float conversion then calls the rails standard `number_to_currency`.
235
+
236
+ When the user clicks 'Add To My Shopping Cart' the product will be added to the cart. A flash message is displayed, and the user will return to the same page.
237
+
238
+ ### My Cart
239
+
240
+ We still need to create a link to the Shopping Cart page so that the user can view their cart. On your site's main menu:
241
+
242
+ ```ruby
243
+ = link_to_current_cart() # To display Cart (3) when there are 3 items
244
+ ```
245
+
246
+ or
247
+
248
+ ```ruby
249
+ = link_to_current_cart(:label => 'Shopping Cart', :class => 'btn btn-prmary') # To display Shopping Cart (3) when there are 3 items
250
+ ```
251
+
252
+ or
253
+
254
+ ```ruby
255
+ = link_to 'My Cart', effective_orders.carts_path
256
+ ```
257
+
258
+ ### Checkout
259
+
260
+ The checkout screen can be reached through the My Cart page, or linked to directly via
261
+
262
+ ```ruby
263
+ = link_to_checkout() # To display Proceed to Checkout
264
+ ```
265
+
266
+ or
267
+
268
+ ```ruby
269
+ = link_to_checkout(:label => 'Continue to Checkout', :class => 'btn btn-primary')
270
+ ```
271
+
272
+ or
273
+
274
+ ```ruby
275
+ = link_to 'Go Checkout Already', effective_orders.new_order_path
276
+ ```
277
+
278
+ From here, the effective_orders engine takes over, walks the user through billing and shipping details screens, then finally collects payment through one of the configured payment processors.
279
+
280
+
281
+ ## Acts As Purchasable
282
+
283
+ Mark your rails model with the mixin `acts_as_purchasable` to use it with the effective_orders gem.
284
+
285
+ This mixin sets up the relationships and provides some validations on price and such.
286
+
287
+ ### Methods
288
+
289
+ acts_as_purchasable provides the following methods:
290
+
291
+ `.purchased?` has this been purchased by any user in any order?
292
+
293
+ `.purchased_by?(user)` has this been purchased by the given user?
294
+
295
+ `.purchased_orders` returns the `Effective::Order`s in which the purchases have been made
296
+
297
+ ### Scopes
298
+
299
+ acts_as_purchsable provides the following scopes:
300
+
301
+ `Product.purchased` all the Products that have been purchased
302
+
303
+ `Product.purchased_by(user)` all the Products purchased by a given user.
304
+
305
+ `Product.sold` all the Products that have been solid (same as purchased)
306
+
307
+ `Product.sold_by(user)` all the Products that this user has sold via Stripe Connect
308
+
309
+ `Product.not_purchased` all unpurchased Products
310
+
311
+ ### Digital Downloads
312
+
313
+ If your product is a digital download, simply create a method in your acts_as_purchasable rails model that returns the full URL to download.
314
+
315
+ The download link will be displayed on all purchased order receipts and the Order History page.
316
+
317
+ ```ruby
318
+ def purchased_download_url
319
+ 'http://www.something.com/my_cool_product.zip'
320
+ end
321
+ ```
322
+
323
+ Of course, there's no mechanism here to prevent someone from just copy&pasting this URL to a friend.
324
+
325
+ If you're interested in that kind of restricted-download functionality, please check out [effective_assets](https://github.com/code-and-effect/effective_assets) and the authenticated-read temporary URLs.
326
+
327
+
328
+ ### Tax
329
+
330
+ All `acts_as_purchasable` objects will respond to the boolean method `tax_exempt`.
331
+
332
+ By default, `tax_exempt` is false, meaning that tax must be applied to this item.
333
+
334
+ The tax calculation is controlled by the config/initializers/effective_orders.rb `config.tax_rate_method` and may be set on an app wide basis.
335
+
336
+ If `tax_exempt` returns true, it means that no tax will be applied to this item.
337
+
338
+ Please see the initializer for more information.
339
+
340
+
341
+ ### Callbacks
342
+
343
+ When defined, upon purchase the following callback will be triggered:
344
+
345
+ ```ruby
346
+ class Product
347
+ acts_as_purchasable
348
+
349
+ after_purchase do |order, order_item| # These are optional, if you don't care about the order or order_item
350
+ self.do_something() # self is the newly purchased instance of this Product
351
+ end
352
+
353
+ end
354
+ ```
355
+
356
+ ## Authorization
357
+
358
+ All authorization checks are handled via the config.authorization_method found in the `config/initializers/effective_orders.rb` file.
359
+
360
+ It is intended for flow through to CanCan or Pundit, but neither of those gems are required.
361
+
362
+ This method is called by the controller action with the appropriate action and resource
363
+
364
+ This method is called by all controller actions with the appropriate action and resource
365
+
366
+ Action will be one of [:index, :show, :new, :create, :edit, :update, :destroy]
367
+
368
+ Resource will the appropriate Effective::Order, Effective::Cart or Effective::Subscription ActiveRecord object or class
369
+
370
+ The authorization method is defined in the initializer file:
371
+
372
+ ```ruby
373
+ # As a Proc (with CanCan)
374
+ config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) }
375
+ ```
376
+
377
+ ```ruby
378
+ # As a Custom Method
379
+ config.authorization_method = :my_authorization_method
380
+ ```
381
+
382
+ and then in your application_controller.rb:
383
+
384
+ ```ruby
385
+ def my_authorization_method(action, resource)
386
+ current_user.is?(:admin) || EffectivePunditPolicy.new(current_user, resource).send('#{action}?')
387
+ end
388
+ ```
389
+
390
+ or disabled entirely:
391
+
392
+ ```ruby
393
+ config.authorization_method = false
394
+ ```
395
+
396
+ If the method or proc returns false (user is not authorized) an Effective::AccessDenied exception will be raised
397
+
398
+ You can rescue from this exception by adding the following to your application_controller.rb:
399
+
400
+ ```ruby
401
+ rescue_from Effective::AccessDenied do |exception|
402
+ respond_to do |format|
403
+ format.html { render 'static_pages/access_denied', :status => 403 }
404
+ format.any { render :text => 'Access Denied', :status => 403 }
405
+ end
406
+ end
407
+ ```
408
+
409
+ ### Permissions
410
+
411
+ The permissions you actually want to define are as follows (using CanCan):
412
+
413
+ ```ruby
414
+ can [:manage], Effective::Cart, :user_id => user.id
415
+ can [:manage], Effective::Order, :user_id => user.id # Orders cannot be deleted
416
+ can [:manage], Effective::Subscription, :user_id => user.id
417
+ ```
418
+
419
+ ## Whats Included
420
+
421
+ This gem has a lot of screens, all of which are automatically available via the Rails Engine.
422
+
423
+ Pretty much every screen also has a coresponding helper function that is used in rendering that content.
424
+
425
+ The idea behind this implementation is that you, the developer, should be able to use effective_orders as a quick drop-in purchasing solution, with all screens and routes provided, but also have all the individual pieces available to customize the workflow.
426
+
427
+
428
+ ### Carts
429
+
430
+ The standard website shopping cart paradigm. Add one or more objects to the cart and purchase them all in one step.
431
+
432
+ When a non-logged-in user comes to the website, a new `Effective::Cart` object is created and stored in the session variable. This user can add items to the Cart as normal.
433
+
434
+ Only when the user proceeds to Checkout will they be required to login.
435
+
436
+ Upon log in, the session Cart will be assigned to that User's ID, and if the User had a previous existing cart, all CartItems will be merged.
437
+
438
+
439
+
440
+ You shouldn't need to deal with the Cart object at all, except to make a link from your Site Menu to the 'My Cart' page (as documented above).
441
+
442
+ However, if you want to render a Cart on another page, or play with the Cart object directly, you totally can.
443
+
444
+ Use the helper method `current_cart` to refer to the current `Effective::Cart`.
445
+
446
+ And call `render_cart(current_cart)` to display the Cart anywhere.
447
+
448
+
449
+ ### Orders
450
+
451
+ On the Checkout page (`effective_orders.new_order_path`) a new `Effective::Order` object is created and one or more `Effective::OrderItem`s are initialized based on the `current_cart`.
452
+
453
+ If the configuration options `config.require_billing_address` and/or `config.require_shipping_address` options are `true` then the user will be prompted for the appropriate addresses, based on [effective_addresses](https://github.com/code-and-effect/effective_addresses/).
454
+
455
+ As well, if the config option `config.collect_user_fields` is present, form fields to collect those user attributes will be present on this page.
456
+
457
+ When the user submits the form on this screen, a POST to `effective_orders.order_path` is made, and the `Effective::Order` object is validated and created.
458
+
459
+ On this final checkout screen, links to all configured payment providers are displayed, and the user may choose which payment processor should be used to make a payment.
460
+
461
+ The payment processor handles collecting the Credit Card number, and through one way or another, the `Effective::Order` `@order.purchase!` method is called.
462
+
463
+ Once the order has been marked purchased, the user is redirected to the `effective_orders.order_purchased_path` screen where they see a 'Thank You!' message, and the Order receipt.
464
+
465
+ If the configuration option `config.mailer[:send_order_receipt_to_buyer] == true` the order receipt will be emailed to the user.
466
+
467
+ As well, if the configuration option `config.mailer[:send_order_receipt_to_admin] == true` the order receipt will be emailed to the site admin.
468
+
469
+ The Order has now been purchased.
470
+
471
+
472
+ If you are using effective_orders to roll your own custom payment workflow, you should be aware of the following helpers:
473
+
474
+ - `render_checkout(order)` to display the standard Checkout step inline.
475
+ - `render_checkout(order, :purchased_redirect_url => '/', :declined_redirect_url => '/')` to display the Checkout step with custom redirect paths.
476
+
477
+ - `render_purchasables(one_or_more_acts_as_purchasable_objects)` to display a list of purchasable items
478
+
479
+ - `render_order(order)` to display the full Order receipt.
480
+ - `order_summary(order)` to display some quick details of an Order and its OrderItems.
481
+ - `order_payment_to_html(order)` to display the payment processor details for an order's payment transaction.
482
+
483
+
484
+ ### Effective::Order Model
485
+
486
+ There may be times where you want to deal with the `Effective::Order` object directly.
487
+
488
+ The `acts_as_purchasable` `.purchased?` and `.purchased_by?(user)` methods only return true when a purchased `Effective::Order` exists for that object.
489
+
490
+ To programatically purchase one or more `acts_as_purchasable` objects:
491
+
492
+ ```ruby
493
+ Effective::Order.new([@product1, @product2], current_user).purchase!('from my rake task')
494
+ ```
495
+
496
+ Here the `billing_address` and `shipping_address` are copied from the `current_user` object if the `current_user` responds_to `billing_address` / `shipping_address` as per [effective_addresses](https://github.com/code-and-effect/effective_addresses/).
497
+
498
+ Here's another example of programatically purchasing some `acts_as_purchasable` objects:
499
+
500
+ ```ruby
501
+ order = Effective::Order.new()
502
+ order.user = @user
503
+ order.billing_address = Effective::Address.new(...)
504
+ order.shipping_address = Effective::Address.new(...)
505
+ order.add(@product1)
506
+ order.add(@product2)
507
+ order.purchase!(:some => {:complicated => 'details', :in => 'a hash'})
508
+ ```
509
+
510
+ The one gotcha with the above two scenarios, is that when `purchase!` is called, the `Effective::Order` in question will run through its validations. These validations include:
511
+
512
+ - `validates_presence_of :billing_address` when configured to be required
513
+ - `validates_presence_of :shipping_address` when configured to be required
514
+ - `validates :user` which can be disabled via config initializer
515
+
516
+ - `validates_numericality_of :total, :greater_than_or_equal_to => minimum_charge` where minimum_charge is the configured value, once again from the initializer
517
+ - `validates_presence_of :order_items` the Order must have at least one OrderItem
518
+
519
+ You can skip validations with the following command, but be careful as this skips all validations:
520
+
521
+ ```ruby
522
+ Effective::Order.new(@product, @user).purchase!('no validations', :validate => false)
523
+ ```
524
+
525
+ The `@product` is now considered purchased.
526
+
527
+
528
+ To check an Order's purchase state, you can call `@order.purchased?`
529
+
530
+ There also exist the scopes: `Effective::Order.purchased` and `Effective::Order.purchased_by(user)` which return a chainable relation of all purchased `Effective::Order` objects.
531
+
532
+
533
+ ### My Purchases / Order History
534
+
535
+ This screen displays all past purchases made by the current user. You can add it to your site's main menu or User profile area:
536
+
537
+ ```ruby
538
+ = link_to_my_purchases() # To display My Purchases
539
+ ```
540
+
541
+ or
542
+
543
+ ```ruby
544
+ = link_to_my_purchases(:label => 'Order History', :class => 'btn btn-primary')
545
+ ```
546
+
547
+ or
548
+
549
+ ```ruby
550
+ = link_to 'My Order History', effective_orders.my_purchases_path
551
+ ```
552
+
553
+ or render it inline on an existing page with
554
+
555
+ ```ruby
556
+ render_order_history(user_or_orders)
557
+ ```
558
+
559
+ If a user is passed, a call to `Effective::Order.purchased_by(user)` will be made to assign all purchased orders.
560
+
561
+ Totally optional, but another way of displaying the Order History is to use the included datatable, based on [effective_datatables](https://github.com/code-and-effect/effective_datatables/)
562
+
563
+ In your controller:
564
+
565
+ ```ruby
566
+ @datatable = Effective::Datatables::Orders.new(:user_id => @user.id)
567
+ ```
568
+
569
+ and then in the view:
570
+
571
+ ```ruby
572
+ render_datatable @datatable
573
+ ```
574
+
575
+ Please refer to [effective_datatables](https://github.com/code-and-effect/effective_datatables/) for more information about that gem.
576
+
577
+
578
+ ### Admin Screen
579
+
580
+ To use the Admin screen, please also install the effective_datatables gem:
581
+
582
+ ```ruby
583
+ gem 'effective_datatables'
584
+ ```
585
+
586
+ Then you should be able to visit:
587
+
588
+ ```ruby
589
+ link_to 'Orders', effective_orders.admin_orders_path # /admin/orders
590
+ ```
591
+
592
+ or to create your own Datatable of all Orders, in your controller:
593
+
594
+ ```ruby
595
+ @datatable = Effective::Datatables::Orders.new()
596
+ ```
597
+
598
+ and then in the view:
599
+
600
+ ```ruby
601
+ render_datatable @datatable
602
+ ```
603
+
604
+
605
+ ## Testing in Development
606
+
607
+ Every payment processor seems to have its own development sandbox which allow you to make test purchases in development mode.
608
+
609
+ You will need an external IP address to work with these sandboxes.
610
+
611
+ We suggest the free application `https://ngrok.com/` for this ability.
612
+
613
+
614
+ ## Paying via Moneris
615
+
616
+ Use the following instructions to set up a Moneris TEST store.
617
+
618
+ The set up process is identical to a Production store, except that you will need to Sign Up with Moneris and get real credentials.
619
+
620
+ We are also going to use ngrok to give us a public facing URL
621
+
622
+ ### Create Test / Development Store
623
+
624
+ Visit https://esqa.moneris.com/mpg/ and login with: demouser / store1 / password
625
+
626
+ Select ADMIN -> hosted config from the menu
627
+
628
+ Click the 'Generate a New Configuration' button which should bring us to a "Hosted Paypage Configuration"
629
+
630
+ ### Basic Configuration
631
+
632
+ Description: 'My Test store'
633
+
634
+ Transaction Type: Purchase
635
+
636
+ Response Method: Sent to your server as a POST
637
+
638
+ Approved URL: http://4f972556.ngrok.com/orders/moneris_postback
639
+
640
+ Declined URL: http://4f972556.ngrok.com/orders/moneris_postback
641
+
642
+ Note: The Approved and Declined URLs must match the effective_orders.moneris_postback_path value in your application. By default it is /orders/moneris_postback
643
+
644
+ Click 'Save Changes'
645
+
646
+ ### PsStoreId and HppKey
647
+
648
+ At the top of the main 'Hosted Paypage Configuration' page should be the ps_store_id and hpp_key values.
649
+
650
+ Copy these two values into the appropriate lines of config/effective_orders.rb initializer file.
651
+
652
+ ```ruby
653
+ config.moneris_enabled = true
654
+
655
+ if Rails.env.production?
656
+ config.moneris = {
657
+ :ps_store_id => '',
658
+ :hpp_key => '',
659
+ :hpp_url => 'https://www3.moneris.com/HPPDP/index.php',
660
+ :verify_url => 'https://www3.moneris.com/HPPDP/verifyTxn.php',
661
+ :order_nudge => 0
662
+ }
663
+ else
664
+ config.moneris = {
665
+ :ps_store_id => 'VZ9BNtore1',
666
+ :hpp_key => 'hp1Y5J35GVDM',
667
+ :hpp_url => 'https://esqa.moneris.com/HPPDP/index.php',
668
+ :verify_url => 'https://esqa.moneris.com/HPPDP/verifyTxn.php',
669
+ :order_nudge => 0
670
+ }
671
+ end
672
+ ```
673
+
674
+ ### Paypage Appearance
675
+
676
+ Click 'Configure Appearance' from the main Hosted Paypage Configuration
677
+
678
+ Display item details: true
679
+
680
+ Display customer details: true
681
+
682
+ Display billing address details: true
683
+
684
+ Display shipping address details: true
685
+
686
+ Enable input of Billing, Shipping and extra fields: false
687
+
688
+ Display merchange name: true, if you have an SSL cert
689
+
690
+ Cancel Button Text: 'Cancel'
691
+
692
+ Cancel Button URL: http://4f972556.ngrok.com
693
+
694
+ Click 'Save Appearance Settings'
695
+
696
+ Click 'Return to main configuration'
697
+
698
+ ### Response Fields
699
+
700
+ None of the 'Return...' checkboxes are needed. Leave unchecked.
701
+
702
+ Perform asynchronous data post: false, unchecked
703
+
704
+ Async Response URL: leave blank
705
+
706
+ Click 'Save Response Settings'
707
+
708
+ Click 'Return to main configuration'
709
+
710
+
711
+ ### Security
712
+
713
+ Referring URL: Depends how you're using effective_orders in your application, you can add multiple URLs
714
+ By default, use http://4f972556.ngrok.com/orders/new
715
+
716
+ Enable Card Verification: false, unused
717
+
718
+ Enable Transaction Verification: true
719
+ Response Method: Displayed as key/value pairs on our server.
720
+ Response URL: leave blank
721
+
722
+ Click 'Save Verification Settings'
723
+ Click 'Return to main configuration'
724
+
725
+
726
+ ### Configure Email Receipts
727
+
728
+ effective_orders automatically sends its own receipts.
729
+
730
+ If you'd prefer to use the Moneris receipt, disable email sendouts from the config/effective_orders.rb initializer
731
+
732
+
733
+ ### Purchasing an Order through Moneris
734
+
735
+ With this test store set up, you can make a successful purchase with:
736
+
737
+ Cardholder Name: Any name
738
+ Credit Card Number: 4242 4242 4242 4242
739
+ Expiry Date: Any future date
740
+
741
+ Some gotchas:
742
+
743
+ 1. When using a test store, if your order total price is less than $10, the penny amount may be used to raise an error code.
744
+
745
+ Order totals ending in .00 will be Approved
746
+ Order totals ending in .05 will be Declined
747
+
748
+ And there's a whole bunch more. Please refer to:
749
+
750
+ https://www.google.ca/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CB8QFjAA&url=https%3A%2F%2Fcartthrob.com%2F%3FACT%3D50%26fid%3D25%26aid%3D704_jvVKOeo1a8d3aSYeT3R4%26board_id%3D1&ei=_p1OVOysEpK_sQTZ8oDgCg&usg=AFQjCNHJGH_hEm4kUJAzkvKrzTqEpFnrgA&sig2=XJdE6PZoOY9habWH_B4uWA&bvm=bv.77880786,d.cWc&cad=rja
751
+
752
+ 2. Moneris will not process a duplicate order ID
753
+
754
+ Once Order id=1 has been purchased/declined, you will be unable to purchase an order with id=1 ever again.
755
+
756
+ This is what the moneris order_nudge configuration setting is used for.
757
+
758
+ You can set this to 1000 to start the IDs at 1+1000 instead of 1.
759
+
760
+
761
+ ## Paying via Stripe
762
+
763
+ First register for an account with Stripe
764
+
765
+ https://manage.stripe.com/register
766
+
767
+ and configure your bank accounts appropriately.
768
+
769
+ Then enable Stripe in the app/config/effective_orders.rb initializer and enter your keys.
770
+
771
+ ```ruby
772
+ config.stripe_enabled = true
773
+
774
+ config.stripe = {
775
+ :secret_key => 'sk_live_IKd6HDaYUfoRjflWQTXfFNfc',
776
+ :publishable_key => 'pk_live_liEGn9f0mcxKmoSjoeNbbuE1',
777
+ :currency => 'usd'
778
+ }
779
+ ```
780
+
781
+ You an find these keys from the Stripe Dashbaord -> Your Account (dropdown) -> Account Settings -> API Keys
782
+
783
+ You're ready to accept payments.
784
+
785
+ ### Stripe Connect
786
+
787
+ Stripe Connect allows effective_orders to collect payments on behalf of users.
788
+
789
+ First register your application with Stripe
790
+
791
+ Stripe Dashbaord -> Your Account (dropdown) -> Account Settings -> Apps
792
+
793
+ Register your application
794
+
795
+ Name: Your Application Name
796
+ Website URL: root_url
797
+
798
+ Development:
799
+
800
+ client_id: given by stripe, we need to record this.
801
+ redirect_uri: stripe_connect_redirect_uri_url # http://www.example.com/effective/orders/stripe_connect_redirect_uri
802
+ webhook_uri: none
803
+
804
+ And add these values to the app/config/effective_orders.rb initializer:
805
+
806
+ ```ruby
807
+ config.stripe = {
808
+ :secret_key => 'sk_live_IKd6HDaYUfoRjflWQTXfFNfc',
809
+ :publishable_key => 'pk_live_liEGn9f0mcxKmoSjoeNbbuE1',
810
+ :currency => 'usd',
811
+ :connect_client_id => 'ca_35jLok5G9kosyYF7quTOwcauJjTnUnud'
812
+ }
813
+ ```
814
+
815
+
816
+ ### Stripe Subscriptions
817
+
818
+ Subscriptions and Stripe Connect do not currently work together.
819
+
820
+ Register an additional Webhook, to accept Stripe subscription creation events from Stripe
821
+
822
+ root_url/webhooks/stripe
823
+
824
+
825
+ ### PayPal
826
+
827
+ TODO
828
+
829
+
830
+ ## License
831
+
832
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
833
+
834
+ Code and Effect is the product arm of [AgileStyle](http://www.agilestyle.com/), an Edmonton-based shop that specializes in building custom web applications with Ruby on Rails.
835
+
836
+
837
+ ## Testing
838
+
839
+ The test suite for this gem is mostly complete.
840
+
841
+ Run tests by:
842
+
843
+ ```ruby
844
+ guard
845
+ ```
846
+
847
+
848
+ ## Contributing
849
+
850
+ 1. Fork it
851
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
852
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
853
+ 4. Push to the branch (`git push origin my-new-feature`)
854
+ 5. Bonus points for test coverage
855
+ 6. Create new Pull Request
856
+