spree_paypal_website_standard 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -34,7 +34,7 @@ You may want to implement your own custom logic by adding 'state_machine' hooks.
34
34
 
35
35
  Add to your Spree application Gemfile.
36
36
 
37
- gem "pp_website_standard", :git => "git://github.com/tomash/spree-pp-website-standard.git"
37
+ gem "spree_paypal_website_standard", :git => "git://github.com/tomash/spree-pp-website-standard.git"
38
38
 
39
39
  Run the install rake task to copiy migrations (create payment_notifications table)
40
40
 
@@ -60,10 +60,29 @@ The following configuration options (keys) can be set:
60
60
  :currency_code # default EUR
61
61
  :sandbox_url # paypal url in sandbox mode, default https://www.sandbox.paypal.com/cgi-bin/webscr
62
62
  :paypal_url # paypal url in production, default https://www.paypal.com/cgi-bin/webscr
63
- :populate_address (true/false) # pre-populate fields of billing address based on spree order data
63
+ :populate_address # (true/false) pre-populate fields of billing address based on spree order data
64
+ :encryption # (true/false) use encrypted shopping cart
65
+ :cert_id # id of certificate used to encrypted stuff
66
+ :ipn_secret # secret string for authorizing IPN
64
67
 
65
- Only the first three ones need to be set up in order to get running.
68
+ Only the first three ones need to be set up in order to get running.
66
69
 
70
+ The last three are required for secure, encrypted operation (see below).
71
+
72
+ ## Encryption / Security
73
+
74
+ The payment link can be encrypted using an SSL key pair and a PayPal public key. In order to attempt this encryption, the following elements must be available. If these are not available a normal link will be generated.
75
+
76
+ Spree::Paypal::Config[:encrypted] must be set to true.
77
+ Spree::Paypal::Config[:cert_id] must be set to a valid certificate id.
78
+ Spree::Paypal::Config[:ipn_secret] must be set to a string considered secret.
79
+ Application must have a Rails.root/certs directory with following files:
80
+
81
+ app_cert.pem # application certificate
82
+ app_key.pem # application key
83
+ paypal_cert_#{Rails.env}.pem # paypal certificate
84
+
85
+ The best instructions on what is what, how these files should be generated etc. are [in AsciiCast 143](http://asciicasts.com/episodes/143-paypal-security) (basically the code of this extension is also based on this AsciiCast).
67
86
 
68
87
  ## IPN Notes (outdated!)
69
88
 
@@ -2,12 +2,18 @@ class PaymentNotificationsController < ApplicationController
2
2
  protect_from_forgery :except => [:create]
3
3
  skip_before_filter :restriction_access
4
4
 
5
- def create
5
+ def create
6
+ if(Spree::Paypal::Config[:encryption] && (params[:secret] != Spree::Paypal::Config[:ipn_secret]))
7
+ logger.info "PayPal_Website_Standard: attempt to send an IPN with invalid secret"
8
+ raise Exception
9
+ end
10
+
6
11
  @order = Order.find_by_number(params[:invoice])
7
12
  PaymentNotification.create!(:params => params,
8
13
  :order_id => @order.id,
9
14
  :status => params[:payment_status],
10
15
  :transaction_id => params[:txn_id])
16
+ logger.info "PayPal_Website_Standard: processing payment notification for invoice #{params["invoice"]}, amount is #{params["mc_gross"]} #{params["mc_currency"]}"
11
17
 
12
18
  # this logging stuff won't live here for long...
13
19
 
@@ -17,21 +23,29 @@ class PaymentNotificationsController < ApplicationController
17
23
 
18
24
  #create payment for this order
19
25
  payment = Payment.new
20
- payment.amount = order.total
26
+
27
+ # 1. Assume that if payment notification comes, it's exactly for the amount
28
+ # sent to paypal (safe assumption -- cart can't be edited while on paypal)
29
+ # 2. Can't use Order#total, as it's intercepted by spree-multi-currency
30
+ # which might lead to lots of false "credit owed" payment states
31
+ # (when they should be "complete")
32
+ payment.amount = order.read_attribute(:total)
33
+ logger.info "PayPal_Website_Standard: set payment.amount to #{payment.amount} based on order's total #{order.read_attribute(:total)}"
34
+
21
35
  payment.payment_method = Order.paypal_payment_method
22
36
  order.payments << payment
23
37
  payment.started_processing
24
38
 
25
39
  order.payment.complete
26
- logger.info("order #{order.number} (#{order.id}) -- completed payment")
40
+ logger.info("PayPal_Website_Standard: order #{order.number} (#{order.id}) -- completed payment")
27
41
  while order.state != "complete"
28
42
  order.next
29
- logger.info("advanced state of Order #{order.number} (#{order.id}). current state #{order.state}. thread #{Thread.current.to_s}. issuing callback")
43
+ logger.info("PayPal_Website_Standard: advanced state of Order #{order.number} (#{order.id}). current state #{order.state}. thread #{Thread.current.to_s}. issuing callback")
30
44
  state_callback(:after) # that line will run all _not run before_ callbacks
31
45
  end
32
46
  order.update_totals
33
47
  order.update!
34
- logger.info("Order #{order.number} (#{order.id}) updated successfully, IPN complete")
48
+ logger.info("PayPal_Website_Standard: Order #{order.number} (#{order.id}) updated successfully, IPN complete")
35
49
  end
36
50
 
37
51
  render :nothing => true
@@ -1,6 +1,11 @@
1
1
  Order.class_eval do
2
2
  has_many :payment_notifications
3
3
 
4
+ # SSL certificates for encrypting paypal link
5
+ PAYPAL_CERT_PEM = "#{Rails.root}/certs/paypal_cert_#{Rails.env}.pem"
6
+ APP_CERT_PEM = "#{Rails.root}/certs/app_cert.pem"
7
+ APP_KEY_PEM = "#{Rails.root}/certs/app_key.pem"
8
+
4
9
  def shipment_cost
5
10
  adjustment_total - credit_total
6
11
  end
@@ -12,4 +17,50 @@ Order.class_eval do
12
17
  def self.paypal_payment_method
13
18
  PaymentMethod.select{ |pm| pm.name.downcase =~ /paypal/}.first
14
19
  end
20
+
21
+ def self.use_encrypted_paypal_link?
22
+ Spree::Paypal::Config[:encryption] &&
23
+ Spree::Paypal::Config[:ipn_secret] &&
24
+ Spree::Paypal::Config[:cert_id] &&
25
+ File.exist?(PAYPAL_CERT_PEM) &&
26
+ File.exist?(APP_CERT_PEM) &&
27
+ File.exist?(APP_KEY_PEM)
28
+ end
29
+
30
+ def paypal_encrypted(payment_notifications_url, options = {})
31
+ values = {
32
+ :business => Spree::Paypal::Config[:account],
33
+ :invoice => self.number,
34
+ :cmd => '_cart',
35
+ :upload => 1,
36
+ :currency_code => options[:currency_code] || Spree::Paypal::Config[:currency_code],
37
+ :handling_cart => self.ship_total,
38
+ :return => Spree::Paypal::Config[:success_url],
39
+ :notify_url => payment_notifications_url,
40
+ :charset => "utf-8",
41
+ :cert_id => Spree::Paypal::Config[:cert_id],
42
+ :page_style => Spree::Paypal::Config[:page_style],
43
+ :tax_cart => self.tax_total
44
+ }
45
+
46
+ self.line_items.each_with_index do |item, index|
47
+ values.merge!({
48
+ "amount_#{index + 1}" => item.price,
49
+ "item_name_#{index + 1}" => item.variant.product.name,
50
+ "item_number_#{index + 1}" => item.variant.product.id,
51
+ "quantity_#{index + 1}" => item.quantity
52
+ })
53
+ end
54
+
55
+ encrypt_for_paypal(values)
56
+ end
57
+
58
+ def encrypt_for_paypal(values)
59
+ paypal_cert = File.read(PAYPAL_CERT_PEM)
60
+ app_cert = File.read(APP_CERT_PEM)
61
+ app_key = File.read(APP_KEY_PEM)
62
+ signed = OpenSSL::PKCS7::sign(OpenSSL::X509::Certificate.new(app_cert), OpenSSL::PKey::RSA.new(app_key, ''), values.map { |k, v| "#{k}=#{v}" }.join("\n"), [], OpenSSL::PKCS7::BINARY)
63
+ OpenSSL::PKCS7::encrypt([OpenSSL::X509::Certificate.new(paypal_cert)], signed.to_der, OpenSSL::Cipher::Cipher::new("DES3"), OpenSSL::PKCS7::BINARY).to_s.gsub("\n", "")
64
+ end
65
+
15
66
  end
@@ -6,44 +6,51 @@
6
6
  %>
7
7
  <%= form_tag submit_url do %>
8
8
 
9
- <input id="business" name="business" type="hidden" value="<%= Spree::Paypal::Config[:account] %>" />
10
- <input id="invoice" name="invoice" type="hidden" value="<%= @order.number %>" />
9
+ <%- if(Order.use_encrypted_paypal_link?) %>
10
+ <%= hidden_field_tag(:cmd, "_s-xclick") %>
11
+ <%= hidden_field_tag(:encrypted, @order.paypal_encrypted(payment_notifications_url(:secret => Spree::Paypal::Config[:ipn_secret]))) %>
12
+ <% else %>
11
13
 
12
- <% @order.line_items.each_with_index do |item, index| %>
13
- <%- if item.variant.respond_to?(:paypal_name) %>
14
- <input id="item_name_<%= index + 1 %>" name="item_name_<%= index + 1 %>" type="hidden" value="<%= item.variant.paypal_name %>" />
15
- <% else %>
16
- <input id="item_name_<%= index + 1 %>" name="item_name_<%= index + 1 %>" type="hidden" value="<%= item.variant.product.name %>" />
14
+ <input id="page_style" name="page_style" type="hidden" value="<%= Spree::Paypal::Config[:page_style] %>"/>
15
+ <input id="business" name="business" type="hidden" value="<%= Spree::Paypal::Config[:account] %>" />
16
+ <input id="invoice" name="invoice" type="hidden" value="<%= @order.number %>" />
17
+
18
+ <% @order.line_items.each_with_index do |item, index| %>
19
+ <%- if item.variant.respond_to?(:paypal_name) %>
20
+ <input id="item_name_<%= index + 1 %>" name="item_name_<%= index + 1 %>" type="hidden" value="<%= item.variant.paypal_name %>" />
21
+ <% else %>
22
+ <input id="item_name_<%= index + 1 %>" name="item_name_<%= index + 1 %>" type="hidden" value="<%= item.variant.product.name %>" />
23
+ <% end %>
24
+ <input id="amount_<%= index + 1 %>" name="amount_<%= index + 1 %>" type="hidden" value="<%= item.price %>" />
25
+ <input id="quantity_<%= index + 1 %>" name="quantity_<%= index + 1 %>" type="hidden" value="<%= item.quantity %>" />
26
+ <% end %>
27
+
28
+ <input id="currency_code" name="currency_code" type="hidden" value="<%= Spree::Paypal::Config[:currency_code] %>" />
29
+ <input id="handling_cart" name="handling_cart" type="hidden" value="<%= @order.ship_total %>" />
30
+ <input id="tax_cart" name="tax_cart" type="hidden" value="<%= @order.tax_total %>" />
31
+
32
+
33
+ <input id="cmd" name="cmd" type="hidden" value="_cart" />
34
+ <input type="hidden" name="upload" value="1" />
35
+ <input id="notify_url" name="notify_url" type="hidden" value="<%= payment_notifications_url %>" />
36
+ <input type="hidden" name="rm" value ="2" />
37
+ <input id="return" name="return" type="hidden" value="<%= Spree::Paypal::Config[:success_url] %>" />
38
+
39
+ <input id="charset" name="charset" type="hidden" value="utf-8" />
40
+
41
+ <% if(Spree::Paypal::Config[:populate_address] && @order.bill_address) %>
42
+ <input type="hidden" name="address1" id="address1" value="<%= @order.bill_address.address1 %>" />
43
+ <input type="hidden" name="address2" id="address2" value="<%= @order.bill_address.address2 %>" />
44
+ <input type="hidden" name="city" id="city" value="<%= @order.bill_address.city %>" />
45
+ <input type="hidden" name="country" id="country" value="<%= @order.bill_address.country.iso %>" />
46
+ <input type="hidden" name="email" id="email" value="<%= @order.email %>" />
47
+ <input type="hidden" name="first_name" id="first_name" value="<%= @order.bill_address.firstname %>" />
48
+ <input type="hidden" name="last_name" id="last_name" value="<%= @order.bill_address.lastname %>" />
49
+ <input type="hidden" name="zip" id="zip" value="<%= @order.bill_address.zipcode %>" />
50
+ <input type="hidden" name="night_phone_b" id="night_phone_b" value="<%= @order.bill_address.phone %>" />
17
51
  <% end %>
18
- <input id="amount_<%= index + 1 %>" name="amount_<%= index + 1 %>" type="hidden" value="<%= item.price %>" />
19
- <input id="quantity_<%= index + 1 %>" name="quantity_<%= index + 1 %>" type="hidden" value="<%= item.quantity %>" />
20
52
  <% end %>
21
53
 
22
- <input id="currency_code" name="currency_code" type="hidden" value="<%= Spree::Paypal::Config[:currency_code] %>" />
23
- <input id="handling_cart" name="handling_cart" type="hidden" value="<%= @order.ship_total %>" />
24
- <input id="tax_cart" name="tax_cart" type="hidden" value="<%= @order.tax_total %>" />
25
-
26
-
27
- <input id="cmd" name="cmd" type="hidden" value="_cart" />
28
- <input type="hidden" name="upload" value="1" />
29
- <input id="notify_url" name="notify_url" type="hidden" value="<%= payment_notifications_url %>" />
30
- <input type="hidden" name="rm" value ="2" />
31
- <input id="return" name="return" type="hidden" value="<%= Spree::Paypal::Config[:success_url] %>" />
32
-
33
- <input id="charset" name="charset" type="hidden" value="utf-8" />
34
-
35
- <% if(Spree::Paypal::Config[:populate_address] && @order.bill_address) %>
36
- <input type="hidden" name="address1" id="address1" value="<%= @order.bill_address.address1 %>" />
37
- <input type="hidden" name="address2" id="address2" value="<%= @order.bill_address.address2 %>" />
38
- <input type="hidden" name="city" id="city" value="<%= @order.bill_address.city %>" />
39
- <input type="hidden" name="country" id="country" value="<%= @order.bill_address.country.iso %>" />
40
- <input type="hidden" name="email" id="email" value="<%= @order.email %>" />
41
- <input type="hidden" name="first_name" id="first_name" value="<%= @order.bill_address.firstname %>" />
42
- <input type="hidden" name="last_name" id="last_name" value="<%= @order.bill_address.lastname %>" />
43
- <input type="hidden" name="zip" id="zip" value="<%= @order.bill_address.zipcode %>" />
44
- <input type="hidden" name="night_phone_b" id="night_phone_b" value="<%= @order.bill_address.phone %>" />
45
- <% end %>
46
-
47
54
  <%= image_submit_tag "pp_checkout.gif" %>
48
55
 
49
56
  <% end -%>
@@ -10,7 +10,13 @@ class PaypalConfiguration < Configuration
10
10
 
11
11
  # this stuff is really handy
12
12
  preference :currency_code, :string, :default => "EUR"
13
+ preference :page_style, :string, :default => 'PayPal'
13
14
 
15
+ # encryption / security
16
+ preference :encrypted, :boolean, :default => false
17
+ preference :cert_id, :string, :default => "12345678"
18
+ preference :ipn_secret, :string, :default => "secret"
19
+
14
20
  validates_presence_of :name
15
21
  validates_uniqueness_of :name
16
- end
22
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spree_core'
2
- require 'pp_website_standard_hooks'
2
+ require 'spree_paypal_website_standard_hooks'
3
3
 
4
- module PpWebsiteStandard
4
+ module SpreePaypalWebsiteStandard
5
5
  class Engine < Rails::Engine
6
6
 
7
7
  config.autoload_paths += %W(#{config.root}/lib)
@@ -1,4 +1,4 @@
1
- class PpWebsiteStandardHooks < Spree::ThemeSupport::HookListener
1
+ class SpreePaypalWebsiteStandardHooks < Spree::ThemeSupport::HookListener
2
2
  # custom hooks go here
3
3
  # insert_before :checkout_summary_box, 'checkout/paypal_checkout'
4
4
  end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_paypal_website_standard
3
3
  version: !ruby/object:Gem::Version
4
- hash: 59
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
8
7
  - 8
9
- - 2
10
- version: 0.8.2
8
+ - 3
9
+ version: 0.8.3
11
10
  platform: ruby
12
11
  authors:
13
12
  - Gregg Pollack, Sean Schofield, Tomasz Stachewicz
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2011-04-29 00:00:00 +02:00
17
+ date: 2011-10-10 00:00:00 +02:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -26,16 +25,15 @@ dependencies:
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- hash: 103
30
28
  segments:
31
29
  - 0
32
- - 30
30
+ - 50
33
31
  - 0
34
- version: 0.30.0
32
+ version: 0.50.0
35
33
  type: :runtime
36
34
  version_requirements: *id001
37
- description:
38
- email:
35
+ description: Spree extension for integration with PayPal Website Standard payment
36
+ email: tomekrs@o2.pl
39
37
  executables: []
40
38
 
41
39
  extensions: []
@@ -46,9 +44,9 @@ files:
46
44
  - README.md
47
45
  - LICENSE
48
46
  - lib/spree/paypal/config.rb
49
- - lib/pp_website_standard.rb
47
+ - lib/spree_paypal_website_standard.rb
50
48
  - lib/paypal_configuration.rb
51
- - lib/pp_website_standard_hooks.rb
49
+ - lib/spree_paypal_website_standard_hooks.rb
52
50
  - lib/tasks/pp_website_standard.rake
53
51
  - lib/tasks/install.rake
54
52
  - app/controllers/checkout_controller_decorator.rb
@@ -74,7 +72,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
72
  requirements:
75
73
  - - ">="
76
74
  - !ruby/object:Gem::Version
77
- hash: 57
78
75
  segments:
79
76
  - 1
80
77
  - 8
@@ -85,7 +82,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
82
  requirements:
86
83
  - - ">="
87
84
  - !ruby/object:Gem::Version
88
- hash: 3
89
85
  segments:
90
86
  - 0
91
87
  version: "0"