ecommerce 0.0.2

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