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 +22 -3
- data/app/controllers/payment_notifications_controller.rb +19 -5
- data/app/models/order_decorator.rb +51 -0
- data/app/views/checkout/_paypal_checkout.html.erb +41 -34
- data/lib/paypal_configuration.rb +7 -1
- data/lib/{pp_website_standard.rb → spree_paypal_website_standard.rb} +2 -2
- data/lib/{pp_website_standard_hooks.rb → spree_paypal_website_standard_hooks.rb} +1 -1
- metadata +9 -13
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 "
|
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)
|
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
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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 -%>
|
data/lib/paypal_configuration.rb
CHANGED
@@ -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
|
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
|
-
-
|
10
|
-
version: 0.8.
|
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-
|
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
|
+
- 50
|
33
31
|
- 0
|
34
|
-
version: 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/
|
47
|
+
- lib/spree_paypal_website_standard.rb
|
50
48
|
- lib/paypal_configuration.rb
|
51
|
-
- lib/
|
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"
|