effective_orders 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+