spree_paypal_website_standard 0.8.2 → 0.8.3
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.
- 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"
|