effective_orders 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1004 -0
  4. data/app/assets/images/effective_orders/stripe.png +0 -0
  5. data/app/assets/javascripts/effective_orders.js +6 -0
  6. data/app/assets/javascripts/effective_orders/customers.js.coffee +32 -0
  7. data/app/assets/javascripts/effective_orders/providers/stripe.js.coffee +77 -0
  8. data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +81 -0
  9. data/app/assets/stylesheets/effective_orders.scss +2 -0
  10. data/app/assets/stylesheets/effective_orders/_cart.scss +4 -0
  11. data/app/assets/stylesheets/effective_orders/_order.scss +58 -0
  12. data/app/controllers/admin/customers_controller.rb +24 -0
  13. data/app/controllers/admin/order_items_controller.rb +16 -0
  14. data/app/controllers/admin/orders_controller.rb +223 -0
  15. data/app/controllers/effective/carts_controller.rb +85 -0
  16. data/app/controllers/effective/concerns/purchase.rb +62 -0
  17. data/app/controllers/effective/customers_controller.rb +20 -0
  18. data/app/controllers/effective/orders_controller.rb +162 -0
  19. data/app/controllers/effective/providers/cheque.rb +22 -0
  20. data/app/controllers/effective/providers/free.rb +33 -0
  21. data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
  22. data/app/controllers/effective/providers/moneris.rb +60 -0
  23. data/app/controllers/effective/providers/paypal.rb +33 -0
  24. data/app/controllers/effective/providers/phone.rb +22 -0
  25. data/app/controllers/effective/providers/pretend.rb +26 -0
  26. data/app/controllers/effective/providers/refund.rb +33 -0
  27. data/app/controllers/effective/providers/stripe.rb +72 -0
  28. data/app/controllers/effective/subscripter_controller.rb +18 -0
  29. data/app/controllers/effective/webhooks_controller.rb +109 -0
  30. data/app/datatables/admin/effective_customers_datatable.rb +22 -0
  31. data/app/datatables/admin/effective_orders_datatable.rb +100 -0
  32. data/app/datatables/effective_orders_datatable.rb +79 -0
  33. data/app/helpers/effective_carts_helper.rb +113 -0
  34. data/app/helpers/effective_orders_helper.rb +143 -0
  35. data/app/helpers/effective_paypal_helper.rb +49 -0
  36. data/app/helpers/effective_stripe_helper.rb +85 -0
  37. data/app/helpers/effective_subscriptions_helper.rb +34 -0
  38. data/app/mailers/effective/orders_mailer.rb +196 -0
  39. data/app/models/concerns/acts_as_purchasable.rb +118 -0
  40. data/app/models/concerns/acts_as_subscribable.rb +90 -0
  41. data/app/models/concerns/acts_as_subscribable_buyer.rb +49 -0
  42. data/app/models/effective/access_denied.rb +17 -0
  43. data/app/models/effective/cart.rb +88 -0
  44. data/app/models/effective/cart_item.rb +40 -0
  45. data/app/models/effective/customer.rb +92 -0
  46. data/app/models/effective/order.rb +541 -0
  47. data/app/models/effective/order_item.rb +63 -0
  48. data/app/models/effective/product.rb +23 -0
  49. data/app/models/effective/sold_out_validator.rb +7 -0
  50. data/app/models/effective/subscripter.rb +185 -0
  51. data/app/models/effective/subscription.rb +95 -0
  52. data/app/models/effective/tax_rate_calculator.rb +48 -0
  53. data/app/views/admin/customers/_actions.html.haml +2 -0
  54. data/app/views/admin/customers/index.html.haml +6 -0
  55. data/app/views/admin/customers/show.html.haml +6 -0
  56. data/app/views/admin/order_items/index.html.haml +3 -0
  57. data/app/views/admin/orders/_datatable_actions.html.haml +18 -0
  58. data/app/views/admin/orders/_form.html.haml +35 -0
  59. data/app/views/admin/orders/_form_note_internal.html.haml +7 -0
  60. data/app/views/admin/orders/_order_actions.html.haml +9 -0
  61. data/app/views/admin/orders/_order_item_fields.html.haml +14 -0
  62. data/app/views/admin/orders/checkout.html.haml +3 -0
  63. data/app/views/admin/orders/edit.html.haml +6 -0
  64. data/app/views/admin/orders/index.html.haml +6 -0
  65. data/app/views/admin/orders/new.html.haml +4 -0
  66. data/app/views/admin/orders/show.html.haml +4 -0
  67. data/app/views/effective/carts/_cart.html.haml +28 -0
  68. data/app/views/effective/carts/_cart_actions.html.haml +3 -0
  69. data/app/views/effective/carts/show.html.haml +17 -0
  70. data/app/views/effective/customers/_customer.html.haml +72 -0
  71. data/app/views/effective/customers/_form.html.haml +21 -0
  72. data/app/views/effective/customers/edit.html.haml +4 -0
  73. data/app/views/effective/customers/update.js.erb +5 -0
  74. data/app/views/effective/orders/_checkout_actions.html.haml +3 -0
  75. data/app/views/effective/orders/_checkout_step1.html.haml +4 -0
  76. data/app/views/effective/orders/_checkout_step2.html.haml +37 -0
  77. data/app/views/effective/orders/_datatable_actions.html.haml +2 -0
  78. data/app/views/effective/orders/_fields.html.haml +31 -0
  79. data/app/views/effective/orders/_fields_note.html.haml +7 -0
  80. data/app/views/effective/orders/_fields_terms.html.haml +8 -0
  81. data/app/views/effective/orders/_order.html.haml +11 -0
  82. data/app/views/effective/orders/_order_actions.html.haml +18 -0
  83. data/app/views/effective/orders/_order_deferred.html.haml +9 -0
  84. data/app/views/effective/orders/_order_footer.html.haml +1 -0
  85. data/app/views/effective/orders/_order_header.html.haml +23 -0
  86. data/app/views/effective/orders/_order_items.html.haml +72 -0
  87. data/app/views/effective/orders/_order_notes.html.haml +17 -0
  88. data/app/views/effective/orders/_order_payment.html.haml +24 -0
  89. data/app/views/effective/orders/_order_shipping.html.haml +30 -0
  90. data/app/views/effective/orders/_orders_table.html.haml +23 -0
  91. data/app/views/effective/orders/cheque/_form.html.haml +4 -0
  92. data/app/views/effective/orders/declined.html.haml +12 -0
  93. data/app/views/effective/orders/deferred.html.haml +13 -0
  94. data/app/views/effective/orders/deferred/_form.html.haml +16 -0
  95. data/app/views/effective/orders/edit.html.haml +3 -0
  96. data/app/views/effective/orders/free/_form.html.haml +5 -0
  97. data/app/views/effective/orders/index.html.haml +3 -0
  98. data/app/views/effective/orders/mark_as_paid/_form.html.haml +23 -0
  99. data/app/views/effective/orders/moneris/_form.html.haml +47 -0
  100. data/app/views/effective/orders/new.html.haml +3 -0
  101. data/app/views/effective/orders/paypal/_form.html.haml +5 -0
  102. data/app/views/effective/orders/phone/_form.html.haml +4 -0
  103. data/app/views/effective/orders/pretend/_form.html.haml +8 -0
  104. data/app/views/effective/orders/purchased.html.haml +11 -0
  105. data/app/views/effective/orders/refund/_form.html.haml +5 -0
  106. data/app/views/effective/orders/show.html.haml +6 -0
  107. data/app/views/effective/orders/stripe/_element.html.haml +8 -0
  108. data/app/views/effective/orders/stripe/_form.html.haml +31 -0
  109. data/app/views/effective/orders_mailer/order_error.html.haml +11 -0
  110. data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +2 -0
  111. data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +2 -0
  112. data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +13 -0
  113. data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +13 -0
  114. data/app/views/effective/orders_mailer/refund_notification_to_admin.html.haml +15 -0
  115. data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
  116. data/app/views/effective/orders_mailer/subscription_created.html.haml +13 -0
  117. data/app/views/effective/orders_mailer/subscription_event_to_admin.html.haml +13 -0
  118. data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
  119. data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
  120. data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
  121. data/app/views/effective/orders_mailer/subscription_trialing.html.haml +7 -0
  122. data/app/views/effective/orders_mailer/subscription_updated.html.haml +13 -0
  123. data/app/views/effective/subscripter/_form.html.haml +60 -0
  124. data/app/views/effective/subscripter/_plan.html.haml +23 -0
  125. data/app/views/layouts/effective_orders_mailer_layout.html.haml +25 -0
  126. data/config/effective_orders.rb +279 -0
  127. data/config/routes.rb +70 -0
  128. data/db/migrate/01_create_effective_orders.rb.erb +137 -0
  129. data/lib/effective_orders.rb +243 -0
  130. data/lib/effective_orders/engine.rb +60 -0
  131. data/lib/effective_orders/version.rb +3 -0
  132. data/lib/generators/effective_orders/install_generator.rb +63 -0
  133. data/lib/generators/templates/effective_orders_mailer_preview.rb +120 -0
  134. data/lib/tasks/effective_orders_tasks.rake +69 -0
  135. metadata +276 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f0bcae4f96f01459844e667c3c9c426a2eac78084e2cfcccb3dc630874554bf8
4
+ data.tar.gz: ca5cc298e9277e80c080d743045f6ff469b50d0ba54ed6c0183d5905f7b10b0e
5
+ SHA512:
6
+ metadata.gz: 315a63864050c7dacfe06ac17dfd098bfb4d698490cb214a9293b7446b6f35061dfa1e5919b63ce1684588977ecb1258ad07d8e7d4c056b61cb457de0ac13ec2
7
+ data.tar.gz: 910d703ac1b4c71a4f0ae00666ea16511578c7a39f31b56348bdd34069aa82d0c07978db26d7586c3b3a545f0aa2fa242bedd8ee25d7a02ca4baff2756537162
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,1004 @@
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 Subscriptions.
8
+
9
+ Sends order receipt emails automatically.
10
+
11
+ Has Order History, My Purchases, My Sales and Admin screens.
12
+
13
+ ## Upgrade to effective_orders 4.3
14
+
15
+ Add the migration
16
+
17
+ ```
18
+ add_column :customers, :payment_method_id, :string
19
+ ```
20
+
21
+ ## effective_orders 4.0
22
+
23
+ This is the 4.0 series of effective_orders.
24
+
25
+ This requires Twitter Bootstrap 4 and Rails 5.1+
26
+
27
+ Please check out [Effective Orders 3.x](https://github.com/code-and-effect/effective_orders/tree/bootstrap3) for more information using this gem with Bootstrap 3.
28
+
29
+ ## Getting Started
30
+
31
+ Please first install the [effective_addresses](https://github.com/code-and-effect/effective_addresses), [effective_datatables](https://github.com/code-and-effect/effective_datatables) and [effective_bootstrap](https://github.com/code-and-effect/effective_bootstrap) gems.
32
+
33
+ Add to your Gemfile:
34
+
35
+ ```ruby
36
+ gem 'effective_orders'
37
+ ```
38
+
39
+ Run the bundle command to install it:
40
+
41
+ ```console
42
+ bundle install
43
+ ```
44
+
45
+ Then run the generator:
46
+
47
+ ```ruby
48
+ rails generate effective_orders:install
49
+ ```
50
+
51
+ The generator will install an initializer which describes all configuration options and creates a database migration.
52
+
53
+ 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.
54
+
55
+ Then migrate the database:
56
+
57
+ ```ruby
58
+ rake db:migrate
59
+ ```
60
+
61
+ Require the javascript on the asset pipeline by adding the following to your application.js:
62
+
63
+ ```ruby
64
+ //= require effective_orders
65
+ ```
66
+
67
+ Require the stylesheet on the asset pipeline by adding the following to your application.css:
68
+
69
+ ```ruby
70
+ *= require effective_orders
71
+ ```
72
+
73
+ ## High Level Overview
74
+
75
+ Your rails app creates and displays a list of `acts_as_purchasable` objects, each with a `link_to_add_to_cart(object)`.
76
+
77
+ The user clicks one or more Add to Cart links and adds some purchasables to their cart.
78
+
79
+ 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.
80
+
81
+ The checkout is a 2-page process:
82
+
83
+ The first page collects billing/shipping details and gives the user their final option to 'Change Items'.
84
+
85
+ After clicking 'Save and Continue', the user will be on the collect money page.
86
+
87
+ If the payment processor is PayPal or Moneris, the user will be sent to their website to enter their credit card details.
88
+
89
+ If the payment processor is Stripe, there is an on-screen popup form to collect those details.
90
+
91
+ Once the user has successfully paid, they are redirected to a thank you page displaying the order receipt.
92
+
93
+ An email notification containing the receipt is also sent to the buyer's email address, and the site admin.
94
+
95
+
96
+ ## Usage
97
+
98
+ 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.
99
+
100
+ These purchasables could be Products, EventTickets, Memberships or anything else.
101
+
102
+
103
+ ### Representing Prices
104
+
105
+ All prices should be internally represented as Integers. For us North Americans, think of it as the number of cents.
106
+
107
+ To represent the value of `$10.00` the price method should return `1000`.
108
+
109
+ Similarly, to represent a value of `$0.50` the price method should return `50`.
110
+
111
+ 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.
112
+
113
+
114
+ ### Creating a purchasable
115
+
116
+ Once installed, we still need to create something to purchase.
117
+
118
+ Let's create a `Product` model that uses the `acts_as_purchasable` mixin.
119
+
120
+ We're also going to prevent the Product from being deleted by overriding `def destroy` and instead setting a boolean `archived = true`.
121
+
122
+ If someone purchased a Product which is later deleted, the Order History page will be unable to find the Product.
123
+
124
+ ```ruby
125
+ class Product < ActiveRecord::Base
126
+ acts_as_purchasable
127
+
128
+ # Attributes
129
+ # name :string
130
+ # price :integer, default: 0
131
+ # tax_exempt :boolean, default: false
132
+ # timestamps
133
+
134
+ validates_presence_of :name
135
+ validates_numericality_of :price, greater_than_or_equal_to: 0
136
+ end
137
+ ```
138
+
139
+ The database migration will look like the following:
140
+
141
+ ```ruby
142
+ class CreateProducts < ActiveRecord::Migration
143
+ def self.up
144
+ create_table :products do |t|
145
+ t.string :name
146
+ t.integer :price, :default=>0
147
+ t.boolean :tax_exempt, :default=>false
148
+ t.datetime :updated_at
149
+ t.datetime :created_at
150
+ end
151
+ end
152
+
153
+ def self.down
154
+ drop_table :products
155
+ end
156
+ end
157
+ ```
158
+
159
+ Once the database has been migrated, it is time to scaffold/build the CRUD Product screens to create some Products to sell.
160
+
161
+ ### Products#new/#edit
162
+
163
+ Use an [effective_form_inputs](https://github.com/code-and-effect/effective_form_inputs#effective-price) effective_price input to enter the price.
164
+
165
+ 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.
166
+
167
+ This is available for simple_form, formtastic and Rails default FormBuilder.
168
+
169
+ ```haml
170
+ = simple_form_for(@product) do |f|
171
+ = f.input :name
172
+ = f.input :tax_exempt
173
+ = f.input :price, as: :effective_price
174
+ = f.button :submit
175
+ ```
176
+
177
+ or
178
+
179
+ ```ruby
180
+ = semantic_form_for(@product) do |f|
181
+ = f.input :price, as: :effective_price
182
+ ```
183
+
184
+ or
185
+
186
+ ```haml
187
+ = form_for(@product) do |f|
188
+ = f.effective_price :price
189
+ ```
190
+
191
+ ### Products#show
192
+
193
+ So back on the Product#show page, we will render the product with an Add To Cart link
194
+
195
+ ```haml
196
+ %h4= @product
197
+ %p= price_to_currency(@product.price)
198
+ %p= link_to_add_to_cart(@product, class: 'btn btn-primary', label: 'Add To My Shopping Cart')
199
+ ```
200
+
201
+ Please take note of the `price_to_currency` helper above.
202
+
203
+ 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`.
204
+
205
+ 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.
206
+
207
+ ### My Cart
208
+
209
+ 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:
210
+
211
+ ```ruby
212
+ = link_to_current_cart() # To display Cart (3) when there are 3 items
213
+ ```
214
+
215
+ or
216
+
217
+ ```ruby
218
+ = link_to_current_cart(label: 'Shopping Cart', class: 'btn btn-prmary') # To display Shopping Cart (3) when there are 3 items
219
+ ```
220
+
221
+ or
222
+
223
+ ```ruby
224
+ = link_to 'My Cart', effective_orders.carts_path
225
+ ```
226
+
227
+ ### Checkout
228
+
229
+ The checkout screen can be reached through the My Cart page, or linked to directly via
230
+
231
+ ```ruby
232
+ = link_to_checkout() # To display Proceed to Checkout
233
+ ```
234
+
235
+ or
236
+
237
+ ```ruby
238
+ = link_to_checkout(label: 'Continue to Checkout', class: 'btn btn-primary')
239
+ ```
240
+
241
+ or
242
+
243
+ ```ruby
244
+ = link_to 'Continue to Checkout', effective_orders.new_order_path
245
+ ```
246
+
247
+ 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.
248
+
249
+
250
+ ## Acts As Purchasable
251
+
252
+ Mark your rails model with the mixin `acts_as_purchasable` to use it with the effective_orders gem.
253
+
254
+ This mixin sets up the relationships and provides some validations on price and such.
255
+
256
+ ### Methods
257
+
258
+ acts_as_purchasable provides the following methods:
259
+
260
+ `.purchased?` has this been purchased by any user in any order?
261
+
262
+ `.purchased_by?(user)` has this been purchased by the given user?
263
+
264
+ `.purchased_orders` returns the `Effective::Order`s in which the purchases have been made
265
+
266
+ ### Scopes
267
+
268
+ acts_as_purchsable provides the following scopes:
269
+
270
+ `Product.purchased` all the Products that have been purchased
271
+
272
+ `Product.purchased_by(user)` all the Products purchased by a given user.
273
+
274
+ `Product.not_purchased` all unpurchased Products
275
+
276
+ ### Digital Downloads
277
+
278
+ 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.
279
+
280
+ The download link will be displayed on all purchased order receipts and the Order History page.
281
+
282
+ ```ruby
283
+ def purchased_download_url
284
+ 'http://www.something.com/my_cool_product.zip'
285
+ end
286
+ ```
287
+
288
+ Of course, there's no mechanism here to prevent someone from just copy&pasting this URL to a friend.
289
+
290
+ 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.
291
+
292
+
293
+ ### Tax Exempt
294
+
295
+ All `acts_as_purchasable` objects will respond to the boolean method `tax_exempt`.
296
+
297
+ By default, `tax_exempt` is false, meaning that tax must be applied to this item.
298
+
299
+ If `tax_exempt` returns true, it means that no tax will be applied to this item.
300
+
301
+ ### Tax
302
+
303
+ The tax calculation applied to an order is controlled by the config/initializers/effective_orders.rb `config.order_tax_rate_method`
304
+
305
+ The default implementation assigns the tax rate based on the order's billing_address:
306
+
307
+ ```ruby
308
+ config.order_tax_rate_method = Proc.new { |order| Effective::TaxRateCalculator.new(order: order).tax_rate }
309
+ ```
310
+
311
+ Right now, the `Effective::TaxRateCalculator` only supports taxes for Canadian provinces.
312
+
313
+ US and international tax rates are not currently supported and are assigned 0% tax.
314
+
315
+ Instead of calculating based on the billing_address, a single static tax rate can be applied to all orders.
316
+
317
+ To apply 12.5% tax to all orders:
318
+
319
+ ```ruby
320
+ config.order_tax_rate_method = Proc.new { |order| 12.5 }
321
+ ```
322
+
323
+ Or to apply 0% tax:
324
+
325
+ ```ruby
326
+ config.order_tax_rate_method = Proc.new { |order| 0 }
327
+ ```
328
+
329
+ Or, hardcode a country and state code:
330
+
331
+ ```ruby
332
+ config.order_tax_rate_method = Proc.new { |order| Effective::TaxRateCalculator.new(country_code: 'CA', state_code: 'AB').tax_rate }
333
+ ```
334
+
335
+ Please see the initializer file for more information.
336
+
337
+
338
+ ### Callbacks
339
+
340
+ There are three interesting callbacks you can define on the purchasable object, `before_purchase`, `after_purchase` and `after_decline`.
341
+
342
+ The `before_purchase` callback runs just before the `order` object is saved. This callback lets you do things in the same transaction the order is saved in.
343
+
344
+ The `after_purchase` callback runs just after the `order` object is saved. It runs outside and just after the order save transaction.
345
+
346
+ All three of these callbacks will re-raise any exceptions when in development mode, and swallow them in production.
347
+
348
+ When defined, upon purchase the following callback will be triggered:
349
+
350
+ ```ruby
351
+ class Product
352
+ acts_as_purchasable
353
+
354
+ # Will automatically be saved when order is saved
355
+ before_purchase do |order, order_item|
356
+ self.completed_at = Time.zone.now
357
+ end
358
+
359
+ # Won't be automatically saved. You need to call save on your own.
360
+ after_purchase do |order, order_item|
361
+ self.completed_at = Time.zone.now
362
+ save!
363
+ end
364
+
365
+ end
366
+ ```
367
+
368
+ ## Authorization
369
+
370
+ All authorization checks are handled via the config.authorization_method found in the `config/initializers/effective_orders.rb` file.
371
+
372
+ It is intended for flow through to CanCan or Pundit, but neither of those gems are required.
373
+
374
+ This method is called by the controller action with the appropriate action and resource.
375
+
376
+ Action will be one of [:index, :show, :new, :create, :edit, :update, :destroy]
377
+
378
+ Resource will the appropriate Effective::Order, Effective::Cart or Effective::Subscription ActiveRecord object or class
379
+
380
+ The authorization method is defined in the initializer file:
381
+
382
+ ```ruby
383
+ # As a Proc (with CanCan)
384
+ config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) }
385
+ ```
386
+
387
+ ```ruby
388
+ # As a Custom Method
389
+ config.authorization_method = :my_authorization_method
390
+ ```
391
+
392
+ and then in your application_controller.rb:
393
+
394
+ ```ruby
395
+ def my_authorization_method(action, resource)
396
+ current_user.is?(:admin) || EffectivePunditPolicy.new(current_user, resource).send('#{action}?')
397
+ end
398
+ ```
399
+
400
+ or disabled entirely:
401
+
402
+ ```ruby
403
+ config.authorization_method = false
404
+ ```
405
+
406
+ If the method or proc returns false (user is not authorized) an Effective::AccessDenied exception will be raised
407
+
408
+ You can rescue from this exception by adding the following to your application_controller.rb:
409
+
410
+ ```ruby
411
+ rescue_from Effective::AccessDenied do |exception|
412
+ respond_to do |format|
413
+ format.html { render 'static_pages/access_denied', status: 403 }
414
+ format.any { render text: 'Access Denied', status: 403 }
415
+ end
416
+ end
417
+ ```
418
+
419
+ ### Permissions
420
+
421
+ The permissions you actually want to define for a regular user are as follows (using CanCan):
422
+
423
+ ```ruby
424
+ can [:manage], Effective::Cart, user_id: user.id
425
+ can [:manage], Effective::Order, user_id: user.id # Orders cannot be deleted
426
+ can [:manage], Effective::Subscription, user_id: user.id
427
+ ```
428
+
429
+ In addition to the above, the following permissions allow access to the `/admin` screens:
430
+
431
+ ```ruby
432
+ can :admin, :effective_orders # Can access the admin screens
433
+ ```
434
+
435
+ ## Whats Included
436
+
437
+ This gem has a lot of screens, all of which are automatically available via the Rails Engine.
438
+
439
+ Pretty much every screen also has a coresponding helper function that is used in rendering that content.
440
+
441
+ 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.
442
+
443
+
444
+ ### Carts
445
+
446
+ The standard website shopping cart paradigm. Add one or more objects to the cart and purchase them all in one step.
447
+
448
+ 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.
449
+
450
+ Only when the user proceeds to Checkout will they be required to login.
451
+
452
+ 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.
453
+
454
+
455
+
456
+ 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).
457
+
458
+ However, if you want to render a Cart on another page, or play with the Cart object directly, you totally can.
459
+
460
+ Use the helper method `current_cart` to refer to the current `Effective::Cart`.
461
+
462
+ And call `render_cart(current_cart)` to display the Cart anywhere.
463
+
464
+
465
+ ### Orders
466
+
467
+ 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`.
468
+
469
+ If the configuration options `config.billing_address` and/or `config.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/).
470
+
471
+ If `config.use_address_full_name` is set to `true` then appropriate form field will be shown and the user will be prompted for the appropriate address full name during the checkout process, based on [effective_addresses](https://github.com/code-and-effect/effective_addresses/).
472
+
473
+ 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.
474
+
475
+ 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.
476
+
477
+ The payment processor handles collecting the Credit Card number, and through one way or another, the `Effective::Order` `@order.purchase!` method is called.
478
+
479
+ Once the order has been marked purchased, the user is redirected to the `effective_orders.purchased_order_path` screen where they see a 'Thank You!' message, and the Order receipt.
480
+
481
+ If the configuration option `config.mailer[:send_order_receipt_to_buyer] == true` the order receipt will be emailed to the user.
482
+
483
+ As well, if the configuration option `config.mailer[:send_order_receipt_to_admin] == true` the order receipt will be emailed to the site admin.
484
+
485
+ The Order has now been purchased.
486
+
487
+
488
+ If you are using effective_orders to roll your own custom payment workflow, you should be aware of the following helpers:
489
+
490
+ - `render_checkout(order)` to display the standard Checkout step inline.
491
+ - `render_checkout(order, purchased_url: '/', declined_url: '/')` to display the Checkout step with custom redirect paths.
492
+
493
+ - `render_purchasables(one_or_more_acts_as_purchasable_objects)` to display a list of purchasable items
494
+
495
+ - `render_order(order)` to display the full Order receipt.
496
+ - `order_summary(order)` to display some quick details of an Order and its OrderItems.
497
+ - `order_payment_to_html(order)` to display the payment processor details for an order's payment transaction.
498
+
499
+ #### Send Order Receipts in the Background
500
+
501
+ Emails will be sent immediately unless `config.mailer[:deliver_method] == :deliver_later`.
502
+
503
+ If you are using [Delayed::Job](https://github.com/collectiveidea/delayed_job) to send emails in a background process then you should set the `delayed_job_deliver` option so that `config.mailer[:delayed_job_deliver] == true`.
504
+
505
+
506
+ ### Effective::Order Model
507
+
508
+ There may be times where you want to deal with the `Effective::Order` object directly.
509
+
510
+ The `acts_as_purchasable` `.purchased?` and `.purchased_by?(user)` methods only return true when a purchased `Effective::Order` exists for that object.
511
+
512
+ To programatically purchase one or more `acts_as_purchasable` objects:
513
+
514
+ ```ruby
515
+ Effective::Order.new(@product1, @product2, user: current_user).purchase!(details: 'from my rake task')
516
+ ```
517
+
518
+ 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/).
519
+
520
+ Here's another example of programatically purchasing some `acts_as_purchasable` objects:
521
+
522
+ ```ruby
523
+ order = Effective::Order.new()
524
+ order.user = @user
525
+ order.billing_address = Effective::Address.new(...)
526
+ order.shipping_address = Effective::Address.new(...)
527
+ order.add(@product1)
528
+ order.add(@product2)
529
+ order.purchase!(details: {complicated: 'details', in: 'a hash'})
530
+ ```
531
+
532
+ 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:
533
+
534
+ - `validates_presence_of :billing_address` when configured to be required
535
+ - `validates_presence_of :shipping_address` when configured to be required
536
+ - `validates :user` which can be disabled via config initializer
537
+
538
+ - `validates_numericality_of :total, greater_than_or_equal_to: minimum_charge` where minimum_charge is the configured value, once again from the initializer
539
+ - `validates_presence_of :order_items` the Order must have at least one OrderItem
540
+
541
+ You can skip some buyer validations with the following command:
542
+
543
+ ```ruby
544
+ Effective::Order.new(@product, user: @user).purchase!(skip_buyer_validations: true)
545
+ ```
546
+
547
+ The `@product` is now considered purchased.
548
+
549
+
550
+ To check an Order's purchase state, you can call `@order.purchased?`
551
+
552
+ 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.
553
+
554
+
555
+ ### My Purchases / Order History
556
+
557
+ ```ruby
558
+ = link_to 'Order History', effective_orders.orders_path
559
+ ```
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
+ ### Subscriptions
579
+
580
+ All subscriptions are completed via stripe subscriptions.
581
+
582
+ There is a hardcoded trial mode, that does not reach out to stripe.
583
+
584
+ Every `acts_as_subscribable` object starts as trial mode.
585
+
586
+ Every `acts_as_subscribable_buyer` gets a single stripe subscription, and then can buy quantities of stripe products
587
+
588
+
589
+ #### Stripe setup
590
+
591
+ Create a Product with the name you want customers to see on their receipts
592
+
593
+ Yearly and a Monthly per team
594
+
595
+
596
+ #### Callbacks
597
+
598
+ The event is the Stripe event JSON.
599
+
600
+ ```ruby
601
+ after_invoice_payment_succeeded do |event|
602
+ end
603
+
604
+ after_invoice_payment_failed do |event|
605
+ end
606
+
607
+ after_customer_subscription_created do |event|
608
+ end
609
+
610
+ after_customer_subscription_updated do |event|
611
+ end
612
+
613
+ after_customer_subscription_deleted do |event|
614
+ end
615
+
616
+ after_customer_updated do |event|
617
+ end
618
+ ```
619
+
620
+
621
+ ### Admin Screen
622
+
623
+ To use the Admin screen, please also install the effective_datatables gem:
624
+
625
+ ```ruby
626
+ gem 'effective_datatables'
627
+ ```
628
+
629
+ Then you should be able to visit:
630
+
631
+ ```ruby
632
+ link_to 'Orders', effective_orders.admin_orders_path # /admin/orders
633
+ ```
634
+
635
+ or to create your own Datatable of all Orders, in your controller:
636
+
637
+ ```ruby
638
+ @datatable = Effective::Datatables::Orders.new()
639
+ ```
640
+
641
+ and then in the view:
642
+
643
+ ```ruby
644
+ render_datatable @datatable
645
+ ```
646
+
647
+ ## Rake Tasks
648
+
649
+ ### Overwrite order item names
650
+
651
+ When an order is purchased, the `purchasable_name()` of each `acts_as_purchasable` object is saved to the database. Normally this is just `to_s`.
652
+
653
+ If you change the output of `acts_as_purchasable`.`purchasable_name`, any existing order items will remain unchanged.
654
+
655
+ Run this script to overwrite all saved order item names with the current `acts_as_purchasable`.`purchasable_name`.
656
+
657
+ ```ruby
658
+ rake effective_orders:overwrite_order_item_names
659
+ ```
660
+
661
+ ## Testing in Development
662
+
663
+ Every payment processor seems to have its own development sandbox which allow you to make test purchases in development mode.
664
+
665
+ You will need an external IP address to work with these sandboxes.
666
+
667
+ We suggest the free application `https://ngrok.com/` for this ability.
668
+
669
+
670
+ ## Paying via Moneris
671
+
672
+ Use the following instructions to set up a Moneris TEST store.
673
+
674
+ The set up process is identical to a Production store, except that you will need to Sign Up with Moneris and get real credentials.
675
+
676
+ We are also going to use ngrok to give us a public facing URL
677
+
678
+ ### Create Test / Development Store
679
+
680
+ Visit https://esqa.moneris.com/mpg/ and login with: demouser / store1 / password
681
+
682
+ Select Admin -> Hosted Paypage Config from the menu
683
+
684
+ Click the 'Generate a New Configuration' button which should bring us to a "Hosted Paypage Configuration"
685
+
686
+ ### Basic Configuration
687
+
688
+ Description: My Test Store
689
+
690
+ Transaction Type: Purchase
691
+
692
+ Payment Methods: Credit Cards
693
+
694
+ Response Method: Sent to your server as a POST
695
+
696
+ Approved URL: https://myapp.herokuapp.com/orders/moneris_postback
697
+
698
+ Declined URL: https://myapp.herokuapp.com/orders/moneris_postback
699
+
700
+ Note: The Approved and Declined URLs must match the effective_orders.moneris_postback_orders_path value in your application. By default it is /orders/moneris_postback
701
+
702
+ Use 'Enhanced Cancel': false
703
+ Use 'Enhanced Response Feedback': false
704
+
705
+
706
+ Click 'Save Changes'
707
+
708
+ ### PsStoreId and HppKey
709
+
710
+ At the top of the main 'Hosted Paypage Configuration' page should be the ps_store_id and hpp_key values.
711
+
712
+ Copy these two values into the appropriate lines of config/effective_orders.rb initializer file.
713
+
714
+ ```ruby
715
+ config.moneris_enabled = true
716
+
717
+ if Rails.env.production?
718
+ config.moneris = {
719
+ ps_store_id: '',
720
+ hpp_key: '',
721
+ hpp_url: 'https://www3.moneris.com/HPPDP/index.php',
722
+ verify_url: 'https://www3.moneris.com/HPPDP/verifyTxn.php'
723
+ }
724
+ else
725
+ config.moneris = {
726
+ ps_store_id: 'VZ9BNtore1',
727
+ hpp_key: 'hp1Y5J35GVDM',
728
+ hpp_url: 'https://esqa.moneris.com/HPPDP/index.php',
729
+ verify_url: 'https://esqa.moneris.com/HPPDP/verifyTxn.php'
730
+ }
731
+ end
732
+ ```
733
+
734
+ ### Paypage Appearance
735
+
736
+ Click 'Configure Appearance' from the main Hosted Paypage Configuration
737
+
738
+ Display item details: true
739
+
740
+ Display customer details: true
741
+
742
+ Display billing address details: true
743
+
744
+ Display shipping address details: true
745
+
746
+ Enable input of Billing, Shipping, and extra data fields on the hosted paypage: false
747
+
748
+ Display merchant name: true, if you have an SSL cert
749
+
750
+ Cancel Button Text: Cancel
751
+
752
+ Cancel Button URL: https://myapp.herokuapp.com/
753
+
754
+ Click 'Save Appearance Settings'
755
+
756
+ Click 'Return to main configuration'
757
+
758
+ ### Response/Receipt Data
759
+
760
+ Click 'Configure Response Fields' from the main Hosted Paypage Configuration
761
+
762
+ None of the 'Return...' checkboxes are needed. Leave unchecked.
763
+
764
+ Perform asynchronous data post: false, unchecked
765
+
766
+ Async Response URL: leave blank
767
+
768
+ Click 'Save Response Settings'
769
+
770
+ Click 'Return to main configuration'
771
+
772
+
773
+ ### Security
774
+
775
+ Click 'Configure Security' from the main Hosted Paypage Configuration
776
+
777
+ Referring URL -> Add URL: https://myapp.herokuapp.com/
778
+
779
+ Enable Card Verification: false, unused
780
+
781
+ Enable Transaction Verification: true
782
+
783
+ Response Method: Displayed as key/value pairs on our server.
784
+
785
+ Response URL: leave blank
786
+
787
+ Click 'Save Verification Settings'
788
+
789
+ Click 'Return to main configuration'
790
+
791
+
792
+ ### Configure Email Receipts
793
+
794
+ effective_orders automatically sends its own receipts.
795
+
796
+ If you'd prefer to use the Moneris receipt, disable email sendouts from the config/effective_orders.rb initializer
797
+
798
+
799
+ ### Purchasing an Order through Moneris
800
+
801
+ With this test store set up, you can make a successful purchase with:
802
+
803
+ Cardholder Name: Any name
804
+
805
+ Credit Card Number: 4502 2850 7000 0007
806
+
807
+ Expiry Date: Any future date
808
+
809
+ Some gotchas:
810
+
811
+ 1. When using a test store, there are a whole bunch of ways to simulate failures by posting an order less than $10.00
812
+
813
+ Please refer to:
814
+
815
+ https://developer.moneris.com/en/More/Testing/Penny%20Value%20Simulator
816
+
817
+ The following card will always be approved: 4502 2850 7000 0007
818
+ The following card will always be declined: 4355 3100 0257 6375
819
+
820
+ 2. Moneris will not process a duplicate order ID
821
+
822
+ Once Order id=1 has been purchased/declined, you will be unable to purchase an order with id=1 ever again.
823
+
824
+ effective_orders works around this by appending a timestamp to the order ID.
825
+
826
+
827
+ ## Paying via Stripe
828
+
829
+ Make sure `gem 'stripe'` is included in your Gemfile.
830
+
831
+ First register for an account with Stripe
832
+
833
+ https://manage.stripe.com/register
834
+
835
+ and configure your bank accounts appropriately.
836
+
837
+ Then enable Stripe in the app/config/effective_orders.rb initializer and enter your keys.
838
+
839
+ ```ruby
840
+ config.stripe_enabled = true
841
+
842
+ config.stripe = {
843
+ secret_key: 'sk_live_IKd6HDaYUfoRjflWQTXfFNfc',
844
+ publishable_key: 'pk_live_liEGn9f0mcxKmoSjoeNbbuE1',
845
+ currency: 'usd'
846
+ }
847
+ ```
848
+
849
+ You an find these keys from the Stripe Dashbaord -> Your Account (dropdown) -> Account Settings -> API Keys
850
+
851
+ You're ready to accept payments.
852
+
853
+ ### Stripe Subscriptions
854
+
855
+ To set up stripe subscriptions:
856
+
857
+ Define your model
858
+
859
+ ```ruby
860
+ acts_as_subscribable
861
+ ```
862
+
863
+ and then in your form, to choose a subscription:
864
+
865
+ ```ruby
866
+ = effective_subscription_fields(f, item_wrapper_class: 'col-sm-3')
867
+ ```
868
+
869
+ and in your controller:
870
+
871
+ ```ruby
872
+ @team.save! && @team.subscripter.save!
873
+ ```
874
+
875
+ and in your application controller:
876
+
877
+ ```ruby
878
+ before_action :set_subscription_notice
879
+
880
+ def set_subscription_notice
881
+ return unless team && team.subscription_active? == false
882
+
883
+ if team.trial_expired?
884
+ flash.now[:warning] = 'Your trial has expired'
885
+ elsif team.subscription_active? == false
886
+ flash.now[:warning] = 'Your subscription has become unpaid'
887
+ end
888
+ end
889
+ ```
890
+
891
+ And you can link to the customer#show page
892
+
893
+ ```ruby
894
+ link_to 'Customer', effective_orders.customer_settings_path
895
+ ```
896
+
897
+ To set up stripe:
898
+
899
+ 1.) Set up a stripe account as above.
900
+
901
+ 2.) Ceate one or more plans. Don't include any trial or trial periods.
902
+
903
+ 3.) Subscription Settings: 3-days. Then 1-day, 3-days, 3-days, then Cancel subscription
904
+
905
+ 4.) Click API -> Webhooks and add an endpoint `root_url/webhooks/stripe`. You will need something like ngrok to test this.
906
+
907
+ 5.) Record the webhook Signing secret in `config.subscriptions[:webhook_secret]`
908
+
909
+
910
+ ## Paying Via PayPal
911
+
912
+ Use the following to set up a PayPal sandbox store.
913
+
914
+ ### PayPal Account
915
+
916
+ Start by creating a PayPal Account. [Sign up or login](http://paypal.com/). You'll need a business account for use in production but a personal account is fine for creating sandbox apps.
917
+
918
+ _During sign up of a personal account, you may go to the next step in these directions when PayPal asks you to link a credit card or bank with your account._
919
+
920
+ _During sign up of a business account, you may go to the next step in these directions when PayPal asks "How do you want to set up PayPal on your website?"._
921
+
922
+ Confirm your email address using the email sent to you by PayPal.
923
+
924
+
925
+ ### Configuring Your App With a PayPal Sandbox
926
+
927
+ PayPal uses a series of public and private certificates and keys to communicate with third party applications.
928
+
929
+ You need to generate your application's private key (so that it is private!). To generate these, we'll use OpenSSL. If you're on Mac/Linux, you can run these commands inside `#{Rails.root}/config/paypalcerts/development/`:
930
+
931
+ ```
932
+ openssl genrsa -out app_key.pem 1024
933
+ openssl req -new -key app_key.pem -x509 -days 999 -out app_cert.pem
934
+ ```
935
+
936
+ The app_key.pem file is your private key and the app_cert.pem is the public certificate. We require one more certificate, the PayPal public certificate. This certificate will come from your sandbox seller account.
937
+
938
+ To login to the sandbox seller account:
939
+
940
+ 1. Visit the [PayPal developer portal](https://developer.paypal.com/) and click on "Sandbox" -> "Accounts".
941
+ It might take some time for the two default sandbox accounts to show up here if you just created your account (~10 minutes).
942
+ 2. Click on the facilitator account accordion, then click 'Profile'.
943
+ 3. Change the password of the facilitator account to whatever you want and copy the facilitator account email address.
944
+ 4. Go to the [PayPal sandbox site](https://www.sandbox.paypal.com/).
945
+ 5. Sign in using the facilitator account credentials
946
+
947
+ **If the seller account is from Canada, you can follow these directions:**
948
+
949
+ 1. Click "Profile". Then click "Encrypted Payment Settings" under the "Selling Preferences" column.
950
+ 2. Download the PayPal public certicate in the middle of the page and save it as `#{Rails.root}/config/paypalcerts/development/paypal_cert.pem`.
951
+ 3. Upload the public certificate that you generated earlier, `app_cert.pem`, at the bottom of the page.
952
+ 4. Copy the new `Cert ID` of the new public certificiate and add it to the effective_orders initializer with other PayPal settings as the `:cert_id`.
953
+
954
+ While you're logged in to the seller account, you should disable non-encrypted instant payment notifications (IPNs):
955
+
956
+ 1. Click on "Profile".
957
+ 2. Click on "Website Payment Preferences" under the "Selling Preferences" column.
958
+ 3. Under "Encrypted Website Payments", turn "Block Non-encrypted Website Payment" to "On"
959
+
960
+ **If the seller account is from elsewhere, please contribute what you find. =)**
961
+
962
+ Make sure all of the certificates/keys are available in the proper config directory (i.e. `#{Rails.root}/config/paypalcerts/development/paypal_cert.pem`)
963
+ or set up environment variables to hold the full text or file location.
964
+
965
+ Finally, finish adding config values in the effective_orders initializer. Set `config.paypal_enabled = true` and fill out the `config.paypal` settings:
966
+
967
+ * seller_email - email that you logged into the sandbox site with above
968
+ * secret - can be any string (see below)
969
+ * cert_id - provided by PayPal after uploading your public `app_cert.pem`
970
+ * paypal_url - https://www.sandbox.paypal.com/cgi-bin/webscr for sandbox or https://www.paypal.com/cgi-bin/webscr for real payments
971
+ * currency - [choose your preference](https://developer.paypal.com/docs/integration/direct/rest-api-payment-country-currency-support/)
972
+ * paypal_cert - PayPal's public certificate for your app, downloaded from PayPal earlier (this can be a string with `\n` in it or a path to the file)
973
+ * app_cert - Your generated public certificate (this can be a string with `\n` in it or a path to the file)
974
+ * app_key - Your generated private key (this can be a string with `\n` in it or a path to the file)
975
+
976
+ The secret can be any string. Here's a good way to come up with a secret:
977
+
978
+ ```irb
979
+ & irb
980
+ > require 'securerandom'
981
+ => true
982
+ > SecureRandom.base64
983
+ => "KWidsksL/KR4LAf2EcRSdQ=="
984
+ ```
985
+
986
+ ### Configuring PayPal For Use With Real Payments
987
+
988
+ This process should be very similar although you'll create and configure a seller account on paypal.com rather than the sandbox site.
989
+ You should generate separate private and public certificates/keys for this and it is advisable to not keep production certificates/keys in version control.
990
+
991
+
992
+ ## License
993
+
994
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
995
+
996
+ ## Contributing
997
+
998
+ 1. Fork it
999
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
1000
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
1001
+ 4. Push to the branch (`git push origin my-new-feature`)
1002
+ 5. Bonus points for test coverage
1003
+ 6. Create new Pull Request
1004
+