ecommerce 0.0.2

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 (65) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +96 -0
  5. data/README +256 -0
  6. data/Rakefile +7 -0
  7. data/app/controllers/application_controller.rb +6 -0
  8. data/app/controllers/cart_controller.rb +42 -0
  9. data/app/controllers/ecommerce_controller.rb +5 -0
  10. data/app/controllers/payment_notifications_controller.rb +80 -0
  11. data/app/controllers/photos_controller.rb +19 -0
  12. data/app/controllers/products_controller.rb +35 -0
  13. data/app/helpers/application_helper.rb +3 -0
  14. data/app/helpers/products_helper.rb +8 -0
  15. data/app/models/cart.rb +108 -0
  16. data/app/models/cart_item.rb +39 -0
  17. data/app/models/photo.rb +21 -0
  18. data/app/models/product.rb +29 -0
  19. data/app/views/cart/index.haml +80 -0
  20. data/app/views/layouts/application.html.erb +14 -0
  21. data/app/views/products/_form.html.haml +45 -0
  22. data/app/views/products/edit.html.haml +5 -0
  23. data/app/views/products/index.html.haml +17 -0
  24. data/app/views/products/new.html.haml +4 -0
  25. data/app/views/products/show.haml +28 -0
  26. data/app/views/products/show/_photos.haml +13 -0
  27. data/config.ru +4 -0
  28. data/config/application.rb +43 -0
  29. data/config/boot.rb +13 -0
  30. data/config/database.yml +22 -0
  31. data/config/ecommerce.yml +16 -0
  32. data/config/environment.rb +5 -0
  33. data/config/environments/development.rb +26 -0
  34. data/config/environments/production.rb +49 -0
  35. data/config/environments/test.rb +35 -0
  36. data/config/initializers/ecommerce-config.rb +7 -0
  37. data/config/initializers/mime_types.rb +6 -0
  38. data/config/locales/en.yml +5 -0
  39. data/config/routes.rb +23 -0
  40. data/db/seeds.rb +7 -0
  41. data/doc/README_FOR_APP +2 -0
  42. data/ecommerce-0.0.1.gem +0 -0
  43. data/ecommerce.gemspec +24 -0
  44. data/lib/ecommerce.rb +4 -0
  45. data/lib/ecommerce/ecommerce.rb +5 -0
  46. data/lib/ecommerce/engine.rb +25 -0
  47. data/lib/ecommerce/version.rb +3 -0
  48. data/lib/generators/ecommerce/USAGE +4 -0
  49. data/lib/generators/ecommerce/ecommerce_generator.rb +30 -0
  50. data/lib/generators/ecommerce/templates/migration.rb +59 -0
  51. data/lib/tasks/.gitkeep +0 -0
  52. data/models/payment_notification.rb +15 -0
  53. data/public/404.html +26 -0
  54. data/public/422.html +26 -0
  55. data/public/500.html +26 -0
  56. data/public/favicon.ico +0 -0
  57. data/public/javascripts/ecommerce.js +10 -0
  58. data/public/robots.txt +5 -0
  59. data/public/stylesheets/.gitkeep +0 -0
  60. data/script/rails +6 -0
  61. data/spec/spec_helper.rb +27 -0
  62. data/test/performance/browsing_test.rb +9 -0
  63. data/test/test_helper.rb +13 -0
  64. data/vendor/plugins/.gitkeep +0 -0
  65. metadata +160 -0
@@ -0,0 +1,19 @@
1
+ class PhotosController < InheritedResources::Base
2
+ def new
3
+ @product = Product.find_by_permalink(params['product_id']) || Product.find_by_id(params['product_id'])
4
+ end
5
+
6
+ def create
7
+ @photo = Photo.new(params[:photo])
8
+ @photo.product_id = params[:product_id]
9
+ @photo.save!
10
+ redirect_to :back
11
+ end
12
+
13
+ def destroy
14
+ photo = Photo.find_by_id(params[:id])
15
+ photo.destroy
16
+ redirect_to :back
17
+ end
18
+
19
+ end
@@ -0,0 +1,35 @@
1
+ class ProductsController < InheritedResources::Base
2
+
3
+ unloadable
4
+
5
+ def edit
6
+ @product = Product.find_by_permalink(params['id']) || Product.find_by_id(params['id'])
7
+ edit!
8
+ end
9
+
10
+ def update
11
+ @product = Product.find_by_permalink(params['id']) || Product.find_by_id(params['id'])
12
+ update!
13
+ end
14
+
15
+ def search
16
+ # http://railscasts.com/episodes/120-thinking-sphinx with will_paginate
17
+ # @products = Product.search(params[:q], :page => 1, :per_page => 5, :order => :name)
18
+
19
+ @products = Product.where('name like ? or tags like ?', "%#{params['q']}%", "%#{params['q']}%").paginate(:per_page => 10, :page => params[:page])
20
+
21
+ respond_to do |format|
22
+ format.html { render :template => 'products/search/index' }
23
+ format.xml { render :xml => @products }
24
+ end
25
+ end
26
+
27
+ def by_permalink
28
+ if @product = Product.find_by_permalink(params['path'])
29
+ render :template => 'products/show'
30
+ else
31
+ render :template => 'public/404.html', :status => '404'
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,3 @@
1
+ module ApplicationHelper
2
+
3
+ end
@@ -0,0 +1,8 @@
1
+ module ProductsHelper
2
+
3
+ def permalink(product, target=nil, args={})
4
+ target ||= product.name.downcase
5
+ link_to target, "/p/"+product.permalink, args
6
+ end
7
+
8
+ end
@@ -0,0 +1,108 @@
1
+ class Cart < ActiveRecord::Base
2
+ attr_reader :currency
3
+ include ActionView::Helpers::NumberHelper
4
+
5
+ has_many :cart_items
6
+ # Because 'has_many :cart_items, :as => :items' does not appear to work
7
+ def items ; self.cart_items ; end
8
+
9
+ def purchased?
10
+ self.purchased_at.present?
11
+ end
12
+
13
+ # Add n number of this product to our cart (addition)
14
+ def add(product, quantity=1)
15
+ self.alter_cart_item_quantity(:add, product, quantity.to_i)
16
+ end
17
+
18
+ # Remove a product no matter what the quantity
19
+ def remove(product)
20
+ self.alter_cart_item_quantity(:set, product, 0)
21
+ end
22
+
23
+ # Set the total number of this product in our cart to quantity. Setting to 0 will remove the item
24
+ def set_item_quantity(product, quantity)
25
+ self.alter_cart_item_quantity(:set, product, quantity)
26
+ end
27
+
28
+ def alter_cart_item_quantity(change_type, product, quantity)
29
+ item = self.items.detect{|item| item.product_id == product.id}
30
+ if item
31
+ if quantity >= 1
32
+ case change_type
33
+ when :add then item.increment_quantity(quantity)
34
+ when :set then item.quantity = quantity
35
+ end
36
+ else
37
+ item.remove_from_cart!
38
+ end
39
+ else
40
+ item = self.items.build(:product_id => product.id, :quantity => quantity)
41
+ end
42
+ item.save!
43
+ end
44
+
45
+ def totalf
46
+ Cart.format_price(self.total())
47
+ end
48
+
49
+ def total
50
+ self.items.inject(0) { |sum, cart_item| sum + cart_item.price }
51
+ end
52
+
53
+ def shipping_weight
54
+ self.items.inject(0) { |sum, cart_item| sum + cart_item.shipping_weight }
55
+ end
56
+
57
+ def empty?
58
+ self.items.empty?
59
+ end
60
+
61
+ def empty!
62
+ self.items.each{ |cart_item| cart_item.remove_from_cart! }
63
+ end
64
+
65
+ def item_count
66
+ count = 0
67
+ self.items.each{|item| count += item.quantity}
68
+ count
69
+ end
70
+
71
+ # https://cms.paypal.com/cms_content/en_US/files/developer/PP_OrderMgmt_IntegrationGuide.pdf
72
+ # https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_Appx_websitestandard_htmlvariables
73
+ def paypal_encrypted(return_url, notify_url)
74
+ values = {
75
+ :invoice => self.id,
76
+ :notify_url => notify_url,
77
+ :business => ECO['paypal']['email'],
78
+ :cert_id => ECO['paypal']['cert_id'],
79
+ :cmd => '_cart',
80
+ :upload => 1,
81
+ :weight => self.shipping_weight
82
+ }
83
+
84
+ self.items.each_with_index do |item, index|
85
+ values.merge!({
86
+ "amount_#{index+1}" => item.unit_price,
87
+ "item_name_#{index+1}" => item.name,
88
+ "item_number_#{index+1}" => item.id,
89
+ "quantity_#{index+1}" => item.quantity
90
+ })
91
+ end
92
+ encrypt_for_paypal(values)
93
+ end
94
+
95
+ protected
96
+ def encrypt_for_paypal(values)
97
+ signed = OpenSSL::PKCS7::sign(OpenSSL::X509::Certificate.new(APP_CERT_PEM), OpenSSL::PKey::RSA.new(APP_KEY_PEM, ''), values.map { |k, v| "#{k}=#{v}" }.join("\n"), [], OpenSSL::PKCS7::BINARY)
98
+ OpenSSL::PKCS7::encrypt([OpenSSL::X509::Certificate.new(PAYPAL_CERT_PEM)], signed.to_der, OpenSSL::Cipher::Cipher::new("DES3"), OpenSSL::PKCS7::BINARY).to_s.gsub("\n", "")
99
+ end
100
+
101
+ def self.format_price(price)
102
+ # WTF?!
103
+ # undefined method `number_to_currency' for #<Class:0x370a064
104
+ # number_to_currency(sprintf("%.02f", price))
105
+ sprintf("$%.02f", price)
106
+ end
107
+
108
+ end
@@ -0,0 +1,39 @@
1
+ class CartItem < ActiveRecord::Base
2
+ belongs_to :cart
3
+ belongs_to :product
4
+
5
+ delegate :name, :to => :product
6
+
7
+ def increment_quantity(increment)
8
+ self.quantity += increment
9
+ end
10
+
11
+ def quantity=(quantity)
12
+ if quantity < 1
13
+ self.remove_from_cart!
14
+ else
15
+ write_attribute(:quantity, quantity)
16
+ end
17
+ end
18
+
19
+ def shipping_weight
20
+ self.product.shipping_weight * self.quantity
21
+ end
22
+
23
+ def remove_from_cart!
24
+ self.destroy()
25
+ end
26
+
27
+ # Price per unit
28
+ def unit_price ; self.product.price ; end
29
+ def unit_pricef ; Cart.format_price(self.unit_price()) ; end
30
+
31
+ # Total price
32
+ def price ; self.product.price * self.quantity ; end
33
+ def pricef ; Cart.format_price(self.price()) ; end
34
+
35
+ # needed for rails magic form_for stuff, so DO NOT change this to some debug/display value
36
+ def to_s
37
+ self.product_id
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ class Photo < ActiveRecord::Base
2
+ belongs_to :product
3
+
4
+ scope :photos, :conditions => "hidden != true"
5
+
6
+ NO_IMAGE_PATH = "/assets/not_available/medium/not_found.png"
7
+
8
+ has_attached_file :photo,
9
+ :styles => { :thumb => "95x95#", :large => "450x300" },
10
+ :default_url => "/assets/not_available/:style/not_found.png",
11
+ :url => "/assets/photos/:id/:style/:basename.:extension",
12
+ :path => ":rails_root/public/assets/photos/:id/:style/:basename.:extension"
13
+
14
+ validates_attachment_size :photo, :less_than => 1000.kilobytes
15
+
16
+ validate :photo, :presence => true, :content_type => ['image/jpeg', 'image/jpg', 'image/gif', 'image/png']
17
+
18
+ def alt
19
+ self.photo_file_name.dasherize.titleize.gsub(/\.(jpeg|jpg|gif|png)$/i,'')
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ class Product < ActiveRecord::Base
2
+ include ActionView::Helpers::NumberHelper
3
+
4
+ validates_presence_of :name, :permalink
5
+
6
+ has_many :photos
7
+
8
+ def photos?
9
+ self.photos.present?
10
+ end
11
+
12
+ def price? ; self.price_exists?(self.price) ; end
13
+ def pricef ; self.format_price(self.price) ; end
14
+
15
+ def sale? ; self.price_exists?(self.sale_price) ; end
16
+ def sale_pricef ; self.format_price(self.sale_price) ; end
17
+
18
+ protected
19
+
20
+ def format_price(price)
21
+ number_to_currency(sprintf("%.02f", price))
22
+ end
23
+
24
+ def price_exists?(price)
25
+ price.to_f > 0
26
+ end
27
+
28
+
29
+ end
@@ -0,0 +1,80 @@
1
+
2
+ - if @cart.empty?
3
+ %p#no_items
4
+ There are no items in your cart.
5
+
6
+ - else
7
+ :css
8
+ #cartC div.cart_title {
9
+ font-weight : bold;
10
+ font-size : 1.1em;
11
+ }
12
+ form#cart_purchase input[type="text"] {
13
+ width : 15px;
14
+ margin : 0;
15
+ padding : 2px 4px;
16
+ }
17
+ form#cart_purchase hr {
18
+ background-color : #333;
19
+ }
20
+ div#paypal_checkoutC {
21
+ text-align : center;
22
+ margin-top : 1em;
23
+ }
24
+ div#notesC {
25
+ margin-top : 4em;
26
+ color : #666;
27
+ }
28
+ %div#cartC.prepend-10.span-28
29
+ %div.cart_title.cart_quantity.prepend-2.span-6 Quantity
30
+ %div.cart_title.product_name.span-14 Product Name
31
+ %div.cart_title.price.span-6.last Price
32
+
33
+ = form_tag cart_path, :method => :put, :id => 'cart_purchase' do
34
+ = hidden_field :quantity_total_update, 1
35
+
36
+ - for @cart_item in @cart.items
37
+ %div.span-28.last.clearfix
38
+ %div.cart_quantity.prepend-2.span-6
39
+ = text_field("cart_item[]", 'quantity', :size => 2)
40
+
41
+ %div.cart_item_name.span-14
42
+ = link_to h(@cart_item.name), product_path(@cart_item.product.id)
43
+
44
+ %div.cart_price.span-6.last
45
+ = number_to_currency(@cart_item.price)
46
+
47
+ %div.prepend-1.span-26.append-1
48
+ %hr
49
+
50
+ %div#update_quantityC.span-8
51
+ %button{:type => :submit, :id => 'update_cart'} Update Quantity
52
+
53
+ %div.span-10 &nbsp;
54
+ %div.cart_totalC.span-7.last
55
+ %div.span-4.cart_total_title
56
+ Cart Total :
57
+ %div.span-3.last
58
+ = number_to_currency(@cart.total)
59
+
60
+ %div.prepend-10.span-28
61
+ %div.span-14 &nbsp;
62
+ %div#paypal_checkoutC.span-14.last
63
+ = form_tag ECO['paypal']['url'], :method => :post, :id => :checkout do
64
+ = hidden_field_tag :cmd, "_s-xclick"
65
+ = hidden_field_tag :encrypted, @cart.paypal_encrypted(order_confirmation_path(:paypal), payment_notifications_url(:secret => ECO['paypal']['secret']))
66
+
67
+ %button{:type => :submit, :id => 'checkout'}
68
+ Checkout with
69
+ %strong PayPal
70
+ %div#notesC.prepend-10.span-28
71
+ %h4 What To Expect
72
+ %ul
73
+ %li you will be sent to PayPal to complete this transaction
74
+ %li tax will be applied only to Washington State residents
75
+ %li= "shipping costs are calculated by your total shipping weight (#{@cart.shipping_weight}lbs)"
76
+
77
+ -#
78
+ - form_tag cart_path(@cart), :method => :delete do
79
+ = submit_tag 'Empty Cart', :class => 'button'
80
+
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Ecommerce</title>
5
+ <%= stylesheet_link_tag :all %>
6
+ <%= javascript_include_tag :defaults %>
7
+ <%= csrf_meta_tag %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,45 @@
1
+
2
+ = form_for @product, :html => {:class => 'edit_product scaffold'} do |f|
3
+ = render :partial => 'shared/user_notice', :locals => {:model_obj => @product}
4
+
5
+ %div.formC_700
6
+ .field
7
+ = f.label :name, 'Name'
8
+ = f.text_field :name
9
+
10
+ .field
11
+ = f.label :permalink, 'Permalink'
12
+ %div.disabled_form_field= @product.permalink
13
+
14
+ .field
15
+ = f.label :descr, 'Description'
16
+ = f.text_area :descr
17
+
18
+ .field
19
+ = f.label :price
20
+ = f.text_field :price
21
+
22
+ .field
23
+ = f.label :sale_price
24
+ = f.text_field :sale_price
25
+
26
+ .field
27
+ = f.label :shipping_weight, "Shipping Weight (lbs)"
28
+ = f.text_field :shipping_weight
29
+
30
+
31
+ .field
32
+ = f.label :meta_description, 'Meta Description'
33
+ = f.text_field :meta_description
34
+
35
+ .field
36
+ = f.label :meta_keywords, 'Meta Keywords'
37
+ = f.text_field :meta_keywords
38
+
39
+ .actions.clearfix
40
+ = f.submit 'Save Product Information', :class => :button
41
+
42
+ %hr
43
+ = render :partial => 'photos/form'
44
+ %hr
45
+ = render :partial => 'photos/destroy'
@@ -0,0 +1,5 @@
1
+ %div.clearfix
2
+ = render 'form'
3
+
4
+ %hr.space
5
+ = "Cancel to product page for #{permalink(@product)}".html_safe
@@ -0,0 +1,17 @@
1
+ %h1 Listing products
2
+
3
+ %table
4
+ %tr
5
+ %th
6
+ %th
7
+ %th
8
+
9
+ - @products.each do |product|
10
+ %tr
11
+ %td= link_to 'Show', product
12
+ %td= link_to 'Edit', edit_product_path(product)
13
+ %td= link_to 'Destroy', product, :confirm => 'Are you sure?', :method => :delete
14
+
15
+ %br
16
+
17
+ = link_to 'New Product', new_product_path