jonathantron-paypal 3.0.0pre

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ ._*
7
+ *.orig
8
+ Thumbs.db
9
+ doc
10
+ .yardoc
11
+ .bundle
data/CHANGELOG.md ADDED
@@ -0,0 +1,73 @@
1
+ = 3.0.0 (git)
2
+
3
+ Start spec on Paypal::Helpers::Common.paypal_setup
4
+ Fix specs for Paypal::Config
5
+ Upgrade dev dependencies to use Rspec >= 2.0.0.a
6
+ Introduce a Paypal::Config module to handle ipn/certificates (Jonathan Tron)
7
+ Allow access to params as method (via method_missing) (Jonathan Tron)
8
+ Rework Paypal::Notification :
9
+ Remove methods to get params informations (CAUTION !!! THIS BREAK OLD API) (Jonathan Tron)
10
+ Add methods for all possible statuses and rename #complete? as completed? to follow Paypal naming (Jonathan Tron)
11
+ Use Rack::Utils#parse_query in Paypal::Notification for query parsing (Jonathan Tron)
12
+ Add Spec for notification (Jonathan Tron)
13
+ Add paypal/rails.rb to trigger helper inclusion in view (Jonathan Tron)
14
+ Separate actual helpers in two files (paypal/helpers/common.rb and paypal/helpers/rails.rb) (Jonathan Tron)
15
+ Move files around (Jonathan Tron)
16
+ Update the README and switch to MarkDown (Jonathan Tron)
17
+ Add a CHANGELOG file and switch to new format (Jonathan Tron)
18
+ Remove init.rb (Jonathan Tron)
19
+ Configure gem generation with jelewer (Jonathan Tron)
20
+
21
+ = 2008-10-16 -- 2.0.2
22
+
23
+ NEW: Added block style support for paypal_form_tag (paypal_form_tag do blah.. blah... end)
24
+ DEL: Removed patch for 2.0.0, no longer seems suitable.
25
+ NEW: Added testing for the paypal_form_tag block style.
26
+ NEW: Added a sample (actual from paypal sandbox) PayPal server response for IPN, to aid in testing.
27
+
28
+ = 2008-10-15 -- 2.0.1
29
+
30
+ CHG: Modified README.
31
+ FIX: Moved patch to own directory, was being caught by git-hub as gem's README
32
+ NEW: Added patch for currently installed paypal-2.0.0 gem to apply directly on gems directory.
33
+ NEW: Added relevant test statements.
34
+ FIX: removed duplicate 'invoice' method in lib/notification.rb
35
+ NEW: added correct 'custom' method to lib/notification.rb
36
+ NEW: added pending_reason, reason_code, memo, payment_type, exchange_rate methods to lib/notification.rb
37
+
38
+ = 2006-04-20 -- 2.0.0
39
+
40
+ Uses paypal extended syntax. The plugin can now submit shipping and billing addresses using the paypal_address helper.
41
+
42
+ = 2006-04-20 -- 1.7.0
43
+
44
+ Now a rails plugin
45
+
46
+ = 2006-02-10 -- 1.5.1
47
+
48
+ added complete list of valid paypal options (Paul Hart)
49
+
50
+ = 2006-02-02 -- 1.5.0
51
+
52
+ Now report an error when invalid option is passed to paypal_setup
53
+ Had to rename parameters cancel_url to cancel_return and return_url to return, please update your app
54
+ Improved the test coverage strategy for helper tests
55
+ Added support for encrypted form data (Paul Hart)
56
+
57
+ = 2005-09-16 -- 0.9.6
58
+
59
+ Added readme note about the openssl requirement
60
+
61
+ = 2005-07-26 -- 0.9.5
62
+
63
+ Added tax to the helper parameters
64
+ fixed bug when money class was used to pass in amount. Cents were always 00 (doh!)
65
+ Added invoice and custom optional parameters
66
+ Added charset = utf-8 to all paypal posts
67
+ Wrongly used undefined_quanitity parameter in 0.9.1, this caused users to be prompted for the quanitity on the paypal checkout page... fixed
68
+
69
+ = 2005-07-22 -- 0.9.1
70
+
71
+ support for cancel_url as well as notify_url. This means you can now set the IPN callback address from the paypal_setup method and
72
+ you don't have to do that in the paypal admin interface!
73
+ Removed the actual form tag from the paypal_setup generated code to conform better with docs
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source :gemcutter
2
+
3
+ gem "rack", ">= 1.0.0"
4
+ gem "json_pure"
5
+ gem "rake"
6
+ gem "jeweler"
7
+ gem "rcov", ">= 0.9.8"
8
+ gem "rspec", ">= 2.0.0.a"
9
+ gem "nokogiri"
10
+ gem "bluecloth"
11
+ gem "yard"
12
+ gem "fakeweb", :require => "fakeweb"
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ #--
2
+ # Copyright (c) 2005 Tobias Luetke
3
+ # Copyright (c) 2009 Tron Jonathan
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Welcome to Paypal ruby library
2
+
3
+ This library is here to aid with integrating Paypal payments into ruby on rails
4
+ applications or similar. To set this up you will need to log into your paypal
5
+ business account and tell paypal where to send the IPN ( Instant payment notifications ).
6
+
7
+ # Download
8
+
9
+ * Preferred method of installation is using rubygems. gem install JonathanTron-paypal
10
+ * Alternatively you can get the source code at http://github.com/JonathanTron/paypal
11
+
12
+ # Requirements
13
+
14
+ * Ruby 1.8.2 (may work with previous versions) With OpenSSL support compiled in.
15
+ * Valid paypal business account.
16
+ * (optional) The money library from http://dist.leetsoft.com/api/money
17
+
18
+ # Installation
19
+
20
+ 1. sudo gem install JonathanTron-paypal --source=http://gems.github.com
21
+
22
+ 2. Require the library
23
+
24
+ require "paypal"
25
+
26
+ 2.1. If you're using Rails add :
27
+
28
+ require "paypal/rails"
29
+
30
+ 3. Create a paypal_ipn ( or similar ) action like the one in the "Example action" appendix.
31
+
32
+
33
+ ## -- - TODO : REWRITE BELOW THIS POINT - --
34
+
35
+ Within the new payment controller you can now create pages from which users can be sent to paypal. You always have to sent users to paypal using a HTTP Post so a standard link won't work (well OK but you need some javascript for that). The +Paypal::Helper+ namespace has some examples of how such a forward page may look.
36
+
37
+ # Testing the integration
38
+
39
+ Under https://developer.paypal.com/ you can signup for a paypal developer account.
40
+ This allows you to set up "sandboxed" accounts which work and act like real accounts
41
+ with the difference that no money is exchanged. Its a good idea to sign up for a
42
+ sandbox account to use while the application is running in development mode.
43
+
44
+
45
+ # Example rails controller
46
+
47
+ class BackendController < ApplicationController
48
+
49
+ # Simplification, please write better code then this...
50
+ def paypal_ipn
51
+ notify # Paypal::Notification.new(request.raw_post)
52
+
53
+ if notify.acknowledge
54
+ order # Order.find(notify.item_id)
55
+ order.success # (notify.complete? and order.total # notify.amount) ? 'success' : 'failure'
56
+ order.save
57
+ end
58
+
59
+ render :nothing #> true
60
+ end
61
+ end
62
+
63
+ # Example paypal forward page
64
+
65
+ <%# paypal_form_tag %>
66
+ <%# paypal_setup "Item 500", Money.us_dollar(50000), "bob@bigbusiness.com", :notify_url #> url_for(:only_path #> false, :action #> 'paypal_ipn') %>
67
+
68
+ Please press here to pay $500US using paypal. <br/>
69
+ <%# submit_tag "Go to paypal >>" %>
70
+
71
+ </form>
72
+
73
+ or, with the same results, the block version:
74
+
75
+ <% paypal_form_tag do %>
76
+ <%# paypal_setup "Item 500", Money.us_dollar(50000), "bob@bigbusiness.com", :notify_url #> url_for(:only_path #> false, :action #> 'paypal_ipn') %>
77
+
78
+ Please press here to pay $500US using paypal. <br/>
79
+ <%# submit_tag "Go to paypal >>" %>
80
+
81
+ <% end %>
82
+
83
+ # Using encrypted form data
84
+
85
+ Paypal supports encrypted form data to prevent tampering by third parties.
86
+ You must have a verified paypal account to use this functionality.
87
+
88
+ 1) Create a private key for yourself
89
+
90
+ openssl genrsa -out business_key.pem 1024
91
+
92
+ 2) Create a public certificate to share with Paypal
93
+
94
+ openssl req -new -key business_key.pem -x509 -days 3650 -out business_cert.pem
95
+
96
+ 3) Upload the public certificate to Paypal (under Profile -> Encrypted Payment Settings -> Your Public Certificates -> Add),
97
+ and note the "Cert ID" that Paypal shows for the certificate.
98
+
99
+ 4) Update your controller to include the details for your key and certificate.
100
+
101
+ @business_key # File::read("business_key.pem")
102
+ @business_cert # File::read("business_cert.pem")
103
+ @business_certid # "certid from paypal"
104
+
105
+ 5) Update your views to populate the :business_key, :business_cert and :business_certid options in 'paypal_setup' - the rest of the signature is the same.
106
+
107
+ 6) When you're ready to go live, download the production Paypal certificate and override the default certificate.
108
+
109
+ Paypal::Notification.paypal_cert # File::read("paypal_cert.pem")
110
+
111
+ 7) Finally, add the following line to your environment.rb or inside an initializer:
112
+
113
+ Paypal::Notification.ipn_url # "https://www.paypal.com/cgi-bin/webscr"
114
+
115
+ # Troubleshooting
116
+
117
+ uninitalized constant Paypal - Make sure your ruby has openssl support
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require "rubygems"
2
+ require "rake"
3
+
4
+ $:.unshift "lib"
5
+ require "paypal"
6
+
7
+ begin
8
+ require "jeweler"
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "jonathantron-paypal"
11
+ gem.version = Paypal::VERSION
12
+ gem.summary = %Q{Paypal Express Integration}
13
+ gem.description = %Q{Integrate Paypal Express}
14
+ gem.email = "jonathan@tron.name"
15
+ gem.homepage = "http://github.com/JonathanTron/paypal"
16
+ gem.authors = ["Jonathan Tron", "Joseph Halter", "Tobias Luetke"]
17
+ gem.add_dependency "rack", ">= 1.0.0"
18
+ gem.add_development_dependency "rspec", ">= 2.0.0.a"
19
+ gem.add_development_dependency "rcov", ">= 0.9.8"
20
+ gem.add_development_dependency "nokogiri"
21
+ gem.add_development_dependency "bluecloth"
22
+ gem.add_development_dependency "yard"
23
+ gem.add_development_dependency "fakeweb"
24
+ end
25
+ rescue LoadError
26
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
27
+ end
28
+
29
+ require "rspec/core/rake_task"
30
+ ::Rspec::Core::RakeTask.new(:spec)
31
+ ::Rspec::Core::RakeTask.new(:rcov) do |spec|
32
+ spec.rcov = true
33
+ spec.rcov_opts = "--exclude spec/"
34
+ end
35
+ task :spec => :check_dependencies
36
+ task :default => :spec
37
+
38
+ begin
39
+ require "yard"
40
+ YARD::Rake::YardocTask.new
41
+ rescue LoadError
42
+ task :yardoc do
43
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDoTCCAwqgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMCVVMx
3
+ EzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCFNhbiBKb3NlMRUwEwYDVQQK
4
+ EwxQYXlQYWwsIEluYy4xFjAUBgNVBAsUDXNhbmRib3hfY2VydHMxFDASBgNVBAMU
5
+ C3NhbmRib3hfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0
6
+ MDQxOTA3MDI1NFoXDTM1MDQxOTA3MDI1NFowgZgxCzAJBgNVBAYTAlVTMRMwEQYD
7
+ VQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhTYW4gSm9zZTEVMBMGA1UEChMMUGF5
8
+ UGFsLCBJbmMuMRYwFAYDVQQLFA1zYW5kYm94X2NlcnRzMRQwEgYDVQQDFAtzYW5k
9
+ Ym94X2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG
10
+ 9w0BAQEFAAOBjQAwgYkCgYEAt5bjv/0N0qN3TiBL+1+L/EjpO1jeqPaJC1fDi+cC
11
+ 6t6tTbQ55Od4poT8xjSzNH5S48iHdZh0C7EqfE1MPCc2coJqCSpDqxmOrO+9QXsj
12
+ HWAnx6sb6foHHpsPm7WgQyUmDsNwTWT3OGR398ERmBzzcoL5owf3zBSpRP0NlTWo
13
+ nPMCAwEAAaOB+DCB9TAdBgNVHQ4EFgQUgy4i2asqiC1rp5Ms81Dx8nfVqdIwgcUG
14
+ A1UdIwSBvTCBuoAUgy4i2asqiC1rp5Ms81Dx8nfVqdKhgZ6kgZswgZgxCzAJBgNV
15
+ BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhTYW4gSm9zZTEV
16
+ MBMGA1UEChMMUGF5UGFsLCBJbmMuMRYwFAYDVQQLFA1zYW5kYm94X2NlcnRzMRQw
17
+ EgYDVQQDFAtzYW5kYm94X2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNv
18
+ bYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAFc288DYGX+GX2+W
19
+ P/dwdXwficf+rlG+0V9GBPJZYKZJQ069W/ZRkUuWFQ+Opd2yhPpneGezmw3aU222
20
+ CGrdKhOrBJRRcpoO3FjHHmXWkqgbQqDWdG7S+/l8n1QfDPp+jpULOrcnGEUY41Im
21
+ jZJTylbJQ1b5PBBjGiP0PpK48cdF
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,58 @@
1
+ module Paypal
2
+ module Config
3
+ IPN_URL_SANDBOX = "https://www.sandbox.paypal.com/cgi-bin/webscr"
4
+ IPN_URL_PRODUCTION = "https://www.paypal.com/cgi-bin/webscr"
5
+
6
+ @@mode = :sandbox
7
+ def self.mode=(new_mode)
8
+ raise ArgumentError.new("Paypal::Config.mode should be either :sandbox or :production (you tried to set it as : #{new_mode})") unless [:sandbox, :production].include?(new_mode.to_sym)
9
+ @@mode = new_mode.to_sym
10
+ end
11
+ def self.mode
12
+ @@mode
13
+ end
14
+
15
+ def self.ipn_url
16
+ case @@mode
17
+ when :sandbox
18
+ IPN_URL_SANDBOX
19
+ when :production
20
+ IPN_URL_PRODUCTION
21
+ end
22
+ end
23
+
24
+ def self.ipn_validation_path
25
+ URI.parse(ipn_url).path + "?cmd=_notify-validate"
26
+ end
27
+
28
+ def self.ipn_validation_url
29
+ "#{ipn_url}?cmd=_notify-validate"
30
+ end
31
+
32
+ @@paypal_sandbox_cert = File.read(File.join(File.dirname(__FILE__), 'certs', 'paypal_sandbox.pem'))
33
+ def self.paypal_sandbox_cert=(new_cert)
34
+ @@paypal_sandbox_cert = new_cert
35
+ end
36
+ def self.paypal_sandbox_cert
37
+ @@paypal_sandbox_cert
38
+ end
39
+
40
+ @@paypal_production_cert = nil
41
+ def self.paypal_production_cert=(new_cert)
42
+ @@paypal_production_cert = new_cert
43
+ end
44
+ def self.paypal_production_cert
45
+ @@paypal_production_cert
46
+ end
47
+
48
+ def self.paypal_cert
49
+ case @@mode
50
+ when :sandbox
51
+ @@paypal_sandbox_cert
52
+ when :production
53
+ raise StandardError.new("You should set Paypal::Config.paypal_production_cert with your paypal production certificate") if @@paypal_production_cert.nil?
54
+ @@paypal_production_cert
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,300 @@
1
+ module Paypal
2
+ # This is a collection of helpers which aid in the creation of paypal buttons
3
+ #
4
+ # Example:
5
+ #
6
+ # <%= form_tag Paypal::Config.ipn_url %>
7
+ #
8
+ # <%= paypal_setup "Item 500", Money.us_dollar(50000), "bob@bigbusiness.com" %>
9
+ # Please press here to pay $500US using paypal. <%= submit_tag %>
10
+ #
11
+ # <% end_form_tag %>
12
+ #
13
+ # For this to work you have to include these methods as helpers in your rails application.
14
+ # One way is to add "include Paypal::Helpers" in your application_helper.rb
15
+ # See Paypal::Notification for information on how to catch payment events.
16
+ module Helpers
17
+ module Common
18
+ # This helper creates the hidden form data which is needed for a paypal purchase.
19
+ #
20
+ # * <tt>item_number</tt> -- The first parameter is the item number. This is for your personal organization and can
21
+ # be arbitrary. Paypal will sent the item number back with the IPN so its a great place to
22
+ # store a user ID or a order ID or something like this.
23
+ #
24
+ # * <tt>amount</tt> -- should be a parameter of type Money ( see http://leetsoft.com/api/money ) but can also
25
+ # be a string of type "50.00" for 50$. If you use the string syntax make sure you set the current
26
+ # currency as part of the options hash. The default is USD
27
+ #
28
+ # * <tt>business</tt> -- This is your paypal account name ( an email ). This needs to be a valid paypal business account.
29
+ #
30
+ # The last parameter is a options hash. You can set or override any Paypal-recognized parameter, including:
31
+ #
32
+ # * <tt>:cmd</tt> -- default is '_xclick' or '_xclick-subscriptions' when you use :subscription.
33
+ # * <tt>:quantity</tt> -- default is '1'.
34
+ # * <tt>:no_note</tt> -- default is '1'.
35
+ # * <tt>:item_name</tt> -- default is 'Store purchase'. This is the name of the purchase which will be displayed
36
+ # on the paypal page.
37
+ # * <tt>:no_shipping</tt> -- default is '1'. By default we tell paypal that no shipping is required. Usually
38
+ # the shipping address should be collected in our application, not by paypal.
39
+ # * <tt>:currency</tt> -- default is 'USD'. If you provide a Money object, that will automatically override
40
+ # the value.
41
+ # * <tt>:charset</tt> -- default is 'utf-8'.
42
+ # * <tt>:notify_url</tt> -- If provided paypal will send its IPN notification once a
43
+ # purchase is made, canceled or any other status changes occur.
44
+ # * <tt>:return</tt> -- If provided paypal will redirect a user back to this url after a
45
+ # successful purchase. Useful for a kind of thankyou page.
46
+ # * <tt>:cancel_return</tt> -- If provided paypal will redirect a user back to this url when
47
+ # the user cancels the purchase.
48
+ # * <tt>:tax</tt> -- the tax for the store purchase. Same format as the amount parameter but optional
49
+ # * <tt>:invoice</tt> -- Unique invoice number. User will never see this. optional
50
+ # * <tt>:custom</tt> -- Custom field. User will never see this. optional
51
+ #
52
+ # Dealing with subscriptions
53
+ #
54
+ # * <tt>:subscription</tt> -- Hash containing the subscription options. optional
55
+ # * <tt>:period</tt> -- One of :monthly, :yearly, :weekly or :daily
56
+ # * <tt>:length</tt> -- How often, based on :period. E.g. 6 :monthly?
57
+ # * <tt>:retry</tt> -- Default is false. Boolean for if paypal should retry failed payments.
58
+ # * <tt>:recurring</tt> -- Default is false. Boolean for if paypal should recur payment and end of period.
59
+ #
60
+ # Generating encrypted form data
61
+ #
62
+ # The helper also supports the generation of encrypted button data. Please see the README for more information
63
+ # on the setup and prerequisite steps for using encrypted forms.
64
+ #
65
+ # The following options must all be provided (as strings) to encrypt the data:
66
+ #
67
+ # * <tt>:business_key</tt> -- The private key you have generated
68
+ # * <tt>:business_cert</tt> -- The public certificate you have also uploaded to Paypal
69
+ # * <tt>:business_certid</tt> -- The certificate ID that Paypal has assigned to your certificate.
70
+ #
71
+ # Examples:
72
+ #
73
+ # <%= paypal_setup @order.id, Money.us_dollar(50000), "bob@bigbusiness.com" %>
74
+ # <%= paypal_setup @order.id, '50.00', "bob@bigbusiness.com", :currency => 'USD' %>
75
+ # <%= paypal_setup @order.id, '50.00', "bob@bigbusiness.com", :currency => 'USD', :notify_url => url_for(:only_path => false, :action => 'paypal_ipn') %>
76
+ # <%= paypal_setup @order.id, Money.ca_dollar(50000), "bob@bigbusiness.com", :item_name => 'Snowdevil shop purchase', :return_url => paypal_return_url, :cancel_url => paypal_cancel_url, :notify_url => paypal_ipn_url %>
77
+ # <%= paypal_setup @order.id, Money.ca_dollar(50000), "bob@bigbusiness.com", :item_name => 'Snowdevil shop purchase', :return_url => paypal_return_url, :cancel_url => paypal_cancel_url, :business_key => @business_key, :business_cert => @business_cert, :business_certid => @business_certid %>
78
+ #
79
+ def paypal_setup(item_number, amount, business, options = {})
80
+
81
+ subscription = options.delete(:subscription)
82
+
83
+ misses = (options.keys - valid_setup_options)
84
+ raise ArgumentError, "Unknown option #{misses.inspect}" unless misses.empty?
85
+
86
+ params = {
87
+ :cmd => subscription ? '_ext-enter' : '_xclick',
88
+ :redirect_cmd => subscription ? '_xclick-subscriptions' : nil,
89
+ :quantity => 1,
90
+ :business => business,
91
+ :item_number => item_number,
92
+ :item_name => 'Store purchase',
93
+ :no_shipping => '1',
94
+ :no_note => '1',
95
+ :charset => 'utf-8'
96
+ }.reject{|k,v| v.nil?}.merge(options)
97
+
98
+ params[:currency_code] = amount.currency if amount.respond_to?(:currency)
99
+ params[:currency_code] = params.delete(:currency) if params[:currency]
100
+ params[:currency_code] ||= 'USD'
101
+
102
+ # We accept both strings and money objects as amount
103
+ amount = amount.cents.to_f / 100.0 if amount.respond_to?(:cents)
104
+ amount = sprintf('%.2f', amount)
105
+
106
+ if subscription.nil?
107
+ params[:amount] = amount
108
+ else
109
+ params[:a3] = amount
110
+ params[:p3] = subscription[:length]
111
+ params[:sra] = subscription[:retry] == true ? 1 : 0
112
+ params[:src] = subscription[:recurring] == true ? 1 : 0
113
+ params[:t3] = case subscription[:period]
114
+ when :monthly; 'M'
115
+ when :yearly; 'Y'
116
+ when :weekly; 'W'
117
+ when :daily; 'D'
118
+ end
119
+ end
120
+
121
+ # same for tax
122
+ tax = params[:tax]
123
+ unless tax.nil?
124
+ tax = tax.cents.to_f / 100.0 if tax.respond_to?(:cents)
125
+ params[:tax] = sprintf("%.2f", tax)
126
+ end
127
+
128
+ # look for encryption parameters, save them outsite the parameter hash.
129
+ business_key = params.delete(:business_key)
130
+ business_cert = params.delete(:business_cert)
131
+ business_certid = params.delete(:business_certid)
132
+
133
+ # Build the form
134
+ buttons = []
135
+ # Only attempt an encrypted form if we have all the required fields.
136
+ if business_key and business_cert and business_certid
137
+ require 'openssl'
138
+
139
+ # Convert the key and certificates into OpenSSL-friendly objects.
140
+ paypal_cert = OpenSSL::X509::Certificate.new(Paypal::Config.paypal_cert)
141
+ business_key = OpenSSL::PKey::RSA.new(business_key)
142
+ business_cert = OpenSSL::X509::Certificate.new(business_cert)
143
+ # Put the certificate ID back into the parameter hash the way Paypal wants it.
144
+ params[:cert_id] = business_certid
145
+
146
+ # Prepare a string of data for encryption
147
+ data = ""
148
+ params.each_pair {|k,v| data << "#{k}=#{v}\n"}
149
+
150
+ # Sign the data with our key/certificate pair
151
+ signed = OpenSSL::PKCS7::sign(business_cert, business_key, data, [], OpenSSL::PKCS7::BINARY)
152
+ # Encrypt the signed data with Paypal's public certificate.
153
+ encrypted = OpenSSL::PKCS7::encrypt([paypal_cert], signed.to_der, OpenSSL::Cipher::Cipher::new("DES3"), OpenSSL::PKCS7::BINARY)
154
+
155
+ # The command for encrypted forms is always '_s-xclick'; the real command is in the encrypted data.
156
+ buttons << %Q{<input type="hidden" name="cmd" value="_s-xclick" />}
157
+ buttons << %Q{<input type="hidden" name="cmd" value="#{encrypted}" />}
158
+ else
159
+ # Just emit all the parameters that we have as hidden fields.
160
+ # Note that the sorting isn't really needed, but it makes testing a lot easier for now.
161
+ params.each do |key, value|
162
+ buttons << %Q{<input type="hidden" name="#{key}" value="#{value}" />} unless value.nil?
163
+ end
164
+ end
165
+ buttons.join("\n")
166
+ end
167
+
168
+ # Pass an address to paypal so that all signup forms can be prefilled
169
+ #
170
+ # * <tt>email</tt> -- Customer's email address
171
+ # * <tt>first_name</tt> -- Customer's first name. Must be alpha-numeric, with a 32 character limit
172
+ # * <tt>last_name</tt> -- Customer's last name. Must be alpha-numeric, with a 64 character limit
173
+ # * <tt>address1</tt> -- First line of customer's address. Must be alpha-numeric, with a 100 character limit
174
+ # * <tt>address2</tt> -- Second line of customer's address. Must be alpha-numeric, with a 100 character limit
175
+ # * <tt>city</tt> -- City of customer's address. Must be alpha-numeric, with a 100 character limit
176
+ # * <tt>state</tt> -- State of customer's address. Must be official 2 letter abbreviation
177
+ # * <tt>zip</tt> -- Zip code of customer's address
178
+ # * <tt>night_phone_a</tt> -- Area code of customer's night telephone number
179
+ # * <tt>night_phone_b</tt> -- First three digits of customer's night telephone number
180
+ # * <tt>day_phone_a</tt> -- Area code of customer's daytime telephone number
181
+ # * <tt>day_phone_b</tt> -- First three digits of customer's daytime telephon
182
+ def paypal_address(options = {})
183
+ options.collect do |key, value|
184
+ %Q{<input type="hidden" name="#{key}" value="#{value}" />}
185
+ end.join("\n")
186
+ end
187
+
188
+ private
189
+
190
+ # See https://www.paypal.com/IntegrationCenter/ic_std-variable-reference.html for details on the following options.
191
+ def valid_setup_options
192
+ [
193
+ # Generic Options
194
+ :cmd,
195
+ # IPN Support
196
+ :notify_url,
197
+ # Item Information
198
+ :item_name,
199
+ :quantity,
200
+ :undefined_quantity,
201
+ :on0,
202
+ :os0,
203
+ :on1,
204
+ :os1,
205
+ # Display Information
206
+ :add,
207
+ :cancel_return,
208
+ :cbt,
209
+ :cn,
210
+ :cpp_header_image,
211
+ :cpp_headerback_color,
212
+ :cpp_headerborder_color,
213
+ :cpp_payflow_color,
214
+ :cs,
215
+ :display,
216
+ :image_url,
217
+ :no_note,
218
+ :no_shipping,
219
+ :page_style,
220
+ :return,
221
+ :rm,
222
+ # Transaction Information
223
+ :address_override,
224
+ :currency,
225
+ :currency_code,
226
+ :custom,
227
+ :handling,
228
+ :invoice,
229
+ :redirect_cmd,
230
+ :shipping,
231
+ :tax,
232
+ :tax_cart,
233
+ # Shopping Cart Options
234
+ :amount,
235
+ :business,
236
+ :handling_cart,
237
+ :paymentaction,
238
+ :rupload,
239
+ :charset,
240
+ :upload,
241
+ # Prepopulating PayPal FORMs or Address Overriding
242
+ :address1,
243
+ :address2,
244
+ :city,
245
+ :country,
246
+ :email,
247
+ :first_name,
248
+ :last_name,
249
+ :lc,
250
+ :night_phone_a,
251
+ :night_phone_b,
252
+ :night_phone_c,
253
+ :state,
254
+ :zip,
255
+ # Prepopulating Business Account Sign-up
256
+ :business_address1,
257
+ :business_address2,
258
+ :business_city,
259
+ :business_state,
260
+ :business_country,
261
+ :business_cs_email,
262
+ :business_cs_phone_a,
263
+ :business_cs_phone_b,
264
+ :business_cs_phone_c,
265
+ :business_url,
266
+ :business_night_phone_a,
267
+ :business_night_phone_b,
268
+ :business_night_phone_c,
269
+ # End of list from https://www.paypal.com/IntegrationCenter/ic_std-variable-reference.html
270
+ # The following items are known to exist but are not yet on the above page.
271
+ :business_zip,
272
+ :day_phone_a,
273
+ :day_phone_b,
274
+ :day_phone_c,
275
+ # Subscription Options
276
+ :a1, # Trial Amount 1
277
+ :p1, # Trial Period 1
278
+ :t1, # Trial Period 1 Units (D=days, W=weeks, M=months, Y=years)
279
+ :a2, # Trial Amount 2
280
+ :p2, # Trial Period 2
281
+ :t2, # Trial Period 2 Units
282
+ :a3, # Regular Subscription Amount
283
+ :p3, # Regular Subscription Period
284
+ :t3, # Regular Subscription Period Units
285
+ :src, # Recurring Payments? (1=yes, default=0)
286
+ :sra, # Reattempt Transaction on Failure? (1=yes, default=0)
287
+ :srt, # Recurring Times (number of renewals before auto-cancel, default=forever)
288
+ :usr_manage, # Username and Password Generator? (1=yes, default=0)
289
+ :modify, # Modification Behaviour (0=new subs only, 1=new or modify, 2=modify existing only, default=0)
290
+ # Encryption Options - used internally only.
291
+ :business_key, # Your private key
292
+ :business_cert, # Your public certificate
293
+ :business_certid, # Your public certificate ID (from Paypal)
294
+ # Other
295
+ :bn
296
+ ]
297
+ end
298
+ end
299
+ end
300
+ end