ruby-paypal-extended 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +17 -0
- data/Rakefile +10 -0
- data/lib/ruby-paypal-extended.rb +41 -0
- data/lib/ruby-paypal-extended/caller.rb +82 -0
- data/lib/ruby-paypal-extended/ipn/notification.rb +194 -0
- data/lib/ruby-paypal-extended/operations/mass_pay.rb +89 -0
- data/lib/ruby-paypal-extended/operations/operation.rb +26 -0
- data/lib/ruby-paypal-extended/paypal_exception.rb +6 -0
- data/lib/ruby-paypal-extended/profile.rb +35 -0
- data/lib/ruby-paypal-extended/transaction.rb +17 -0
- data/licenses/LICENSE.txt +44 -0
- data/test/caller_test.rb +39 -0
- data/test/mass_pay_test.rb +64 -0
- data/test/operation_test.rb +29 -0
- data/test/profile_test.rb +31 -0
- data/test/test_helper.rb +4 -0
- metadata +77 -0
data/README
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
== PayPal Ruby SDK - Extended ==
|
2
|
+
|
3
|
+
This is an extension of the Ruby SDK provided by PayPal. The intention is to
|
4
|
+
make some of the function calls a little more object oriented, and a little
|
5
|
+
less hash based.
|
6
|
+
|
7
|
+
This will also be packaged as a gem instead of a Rails plugin (buried inside
|
8
|
+
a Rails project...)
|
9
|
+
|
10
|
+
== Testing ==
|
11
|
+
There is minimal testing of the basic API functionality, because it came
|
12
|
+
pre-written from PayPal, and I'm going to assume that PayPal put it together
|
13
|
+
correctly.
|
14
|
+
|
15
|
+
For new code (and refactoring) there are tests in the /test directory. To run
|
16
|
+
them, use rake:
|
17
|
+
rake test
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'ruby-paypal-extended/paypal_exception'
|
2
|
+
require 'ruby-paypal-extended/profile'
|
3
|
+
require 'ruby-paypal-extended/transaction'
|
4
|
+
require 'ruby-paypal-extended/caller'
|
5
|
+
require 'ruby-paypal-extended/operations/operation'
|
6
|
+
require 'ruby-paypal-extended/operations/mass_pay'
|
7
|
+
require 'ruby-paypal-extended/ipn/notification'
|
8
|
+
|
9
|
+
module PayPalSDK
|
10
|
+
|
11
|
+
# Various configuration values
|
12
|
+
module Config
|
13
|
+
# The PayPal NVP API version that this client can talk to.
|
14
|
+
CLIENT_VERSION = "56.0"
|
15
|
+
|
16
|
+
# The name of this client
|
17
|
+
CLIENT_SOURCE = "ruby-paypal-extended"
|
18
|
+
|
19
|
+
# The hostname for the sandbox API target
|
20
|
+
SANDBOX_API_HOST = "api-3t.sandbox.paypal.com"
|
21
|
+
|
22
|
+
# The path for the sandbox API target
|
23
|
+
SANDBOX_API_PATH = "/nvp"
|
24
|
+
|
25
|
+
# The hostname for the production API target
|
26
|
+
PRODUCTION_API_HOST = "api-3t.paypal.com"
|
27
|
+
|
28
|
+
# The path for the production API target
|
29
|
+
PRODUCTION_API_PATH = "/nvp"
|
30
|
+
|
31
|
+
# The URL to the sandbox IPN endpoint
|
32
|
+
SANDBOX_IPN_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
|
33
|
+
|
34
|
+
# The URL to the productino IPN endpoint
|
35
|
+
PRODUCTION_IPN_URL = 'https://www.paypal.com/cgi-bin/webscr'
|
36
|
+
|
37
|
+
# The Hashes used by Profile
|
38
|
+
SANDBOX_API_ENDPOINT = {"SERVER" => SANDBOX_API_HOST, "SERVICE" => SANDBOX_API_PATH}
|
39
|
+
PRODUCTION_API_ENDPOINT = {"SERVER" => PRODUCTION_API_HOST, "SERVICE" => PRODUCTION_API_PATH}
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'cgi'
|
5
|
+
# The module has a class and a wrapper method wrapping NET:HTTP methods to simplify calling PayPal APIs.
|
6
|
+
|
7
|
+
module PayPalSDK
|
8
|
+
class Caller
|
9
|
+
attr_reader :ssl_strict
|
10
|
+
|
11
|
+
# Headers for ?
|
12
|
+
@@headers = {'Content-Type' => 'html/text'}
|
13
|
+
|
14
|
+
# Creates a new Caller object.
|
15
|
+
#
|
16
|
+
# <tt>profile</tt> - A PayPalSDK::Profile object.
|
17
|
+
def initialize(profile, ssl_verify_mode=false)
|
18
|
+
@ssl_strict = ssl_verify_mode
|
19
|
+
|
20
|
+
@profile = profile
|
21
|
+
|
22
|
+
# Some short names for readability
|
23
|
+
@pi = @profile.proxy_info
|
24
|
+
@cre = @profile.credentials
|
25
|
+
@ci = @profile.client_info
|
26
|
+
@ep = @profile.endpoints
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# This method uses HTTP::Net library to talk to PayPal WebServices. This is the method what merchants should mostly care about.
|
31
|
+
# It expects an hash arugment which has the method name and paramter values of a particular PayPal API.
|
32
|
+
# It assumes and uses the credentials of the merchant which is the attribute value of credentials of profile class in PayPalSDKProfiles module.
|
33
|
+
# It assumes and uses the client information which is the attribute value of client_info of profile class of PayPalSDKProfiles module.
|
34
|
+
# It will also work behind a proxy server. If the calls need be to made via a proxy sever, set USE_PROXY flag to true and specify proxy server and port information in the profile class.
|
35
|
+
def call(request_hash)
|
36
|
+
req_data = request_post_data(request_hash)
|
37
|
+
|
38
|
+
if (@profile.use_proxy?)
|
39
|
+
if( @pi["USER"].nil? || @pi["PASSWORD"].nil? )
|
40
|
+
http = Net::HTTP::Proxy(@pi["ADDRESS"],@pi["PORT"]).new(@ep["SERVER"], 443)
|
41
|
+
else
|
42
|
+
http = Net::HTTP::Proxy(@pi["ADDRESS"],@pi["PORT"],@pi["USER"], @pi["PASSWORD"]).new(@ep["SERVER"], 443)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
http = Net::HTTP.new(@ep["SERVER"], 443)
|
46
|
+
end
|
47
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE #unless ssl_strict
|
48
|
+
http.use_ssl = true;
|
49
|
+
|
50
|
+
contents, unparseddata = http.post2(@ep["SERVICE"], req_data, @headers)
|
51
|
+
data = CGI::parse(unparseddata)
|
52
|
+
transaction = Transaction.new(data)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Builds the post data for sending a request to PayPal, converting
|
56
|
+
# hash values to CGI request (NVP) format.
|
57
|
+
# It expects an hash arugment which has the method name and paramter values of a particular PayPal API.
|
58
|
+
def request_post_data(request_hash)
|
59
|
+
"#{hash2cgiString(request_hash)}&#{hash2cgiString(@cre)}&#{hash2cgiString(@ci)}"
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Method to convert a hash to a string of name and values delimited by '&' as name1=value1&name2=value2...&namen=valuen.
|
65
|
+
def hash2cgiString(h)
|
66
|
+
# Order the keys alphabetically. It's not strictly necessary but makes
|
67
|
+
# output easier to determine (helps testing)
|
68
|
+
# This requires that the keys be strings, since symbols aren't sortable
|
69
|
+
h2 = {}
|
70
|
+
h.each {|key, value| h2[key.to_s] = value}
|
71
|
+
alpha_keys = h2.keys.sort
|
72
|
+
|
73
|
+
# Escape all the values first
|
74
|
+
alpha_keys.each {|key| h2[key] = CGI::escape(h2[key].to_s) if (h2[key])}
|
75
|
+
|
76
|
+
# Create the string
|
77
|
+
alpha_keys.collect {|key| "#{key}=#{h2[key]}"}.join('&')
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,194 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005 Tobias Luetke
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'net/http'
|
25
|
+
require 'net/https'
|
26
|
+
require 'cgi'
|
27
|
+
|
28
|
+
module PayPalSDK
|
29
|
+
module IPN
|
30
|
+
|
31
|
+
# Top-level exceptin class for any IPN related exceptions.
|
32
|
+
class IPNException < PayPalException
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Parser and handler for incoming instant payment notifications from Paypal.
|
37
|
+
# The example shows a typical handler in a Rails application. Note that this
|
38
|
+
# is an example, please read the Paypal API documentation for all the details
|
39
|
+
# on creating a safe payment controller.
|
40
|
+
#
|
41
|
+
# Example
|
42
|
+
#
|
43
|
+
# class BackendController < ApplicationController
|
44
|
+
# def paypal_ipn
|
45
|
+
# notify = Paypal::Notification.new(request.raw_post, false)
|
46
|
+
# order = Order.find(notify.item_id)
|
47
|
+
#
|
48
|
+
# # Verify this IPN with Paypal.
|
49
|
+
# if notify.acknowledge
|
50
|
+
# # Paypal said this IPN is legit.
|
51
|
+
# begin
|
52
|
+
# if notify.complete? && order.total == notify.amount
|
53
|
+
# begin
|
54
|
+
# order.status = 'success'
|
55
|
+
# shop.ship(order)
|
56
|
+
# order.save!
|
57
|
+
# rescue => e
|
58
|
+
# order.status = 'failed'
|
59
|
+
# order.save!
|
60
|
+
# raise
|
61
|
+
# end
|
62
|
+
# else
|
63
|
+
# logger.error("We received a payment notification, but the " <<
|
64
|
+
# "payment doesn't seem to be complete. Please " <<
|
65
|
+
# "investigate. Transaction ID #{notify.transaction_id}.")
|
66
|
+
# end
|
67
|
+
# else
|
68
|
+
# # Paypal said this IPN is not correct.
|
69
|
+
# # ... log possible hacking attempt here ...
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# render :nothing => true
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
class Notification
|
76
|
+
# The IPN URL to connect to. Defaults to production
|
77
|
+
attr_accessor :ipn_url
|
78
|
+
|
79
|
+
# The parsed Paypal IPN data parameters.
|
80
|
+
attr_accessor :params
|
81
|
+
|
82
|
+
# The raw Paypal IPN data that was received.
|
83
|
+
attr_accessor :raw
|
84
|
+
|
85
|
+
# Creates a new Paypal::Notification object. As the first argument,
|
86
|
+
# pass the raw POST data that you've received from Paypal.
|
87
|
+
#
|
88
|
+
# In a Rails application this looks something like this:
|
89
|
+
#
|
90
|
+
# def paypal_ipn
|
91
|
+
# paypal = Paypal::Notification.new(request.raw_post)
|
92
|
+
# ...
|
93
|
+
# end
|
94
|
+
def initialize(post, use_production = false)
|
95
|
+
@ipn_url = use_production ? PayPalSDK::Config::PRODUCTION_IPN_URL : PayPalSDK::Config::SANDBOX_IPN_URL
|
96
|
+
empty!
|
97
|
+
parse(post)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the status of this transaction.
|
101
|
+
def status
|
102
|
+
params['payment_status']
|
103
|
+
end
|
104
|
+
|
105
|
+
# When was this payment received by the client.
|
106
|
+
# sometimes it can happen that we get the notification much later.
|
107
|
+
# One possible scenario is that our web application was down. In this case paypal tries several
|
108
|
+
# times an hour to inform us about the notification
|
109
|
+
def payment_date
|
110
|
+
Time.parse(params['payment_date'])
|
111
|
+
end
|
112
|
+
|
113
|
+
# Id of this transaction (paypal number)
|
114
|
+
def transaction_id
|
115
|
+
params['txn_id']
|
116
|
+
end
|
117
|
+
|
118
|
+
# What type of transaction are we dealing with?
|
119
|
+
def type
|
120
|
+
params['txn_type']
|
121
|
+
end
|
122
|
+
|
123
|
+
# This is the invocie which you passed to paypal
|
124
|
+
def test?
|
125
|
+
params['test_ipn'] == '1'
|
126
|
+
end
|
127
|
+
|
128
|
+
# reset the notification.
|
129
|
+
def empty!
|
130
|
+
@params = {}
|
131
|
+
@raw = ""
|
132
|
+
end
|
133
|
+
|
134
|
+
# Acknowledge the transaction to paypal. This method has to be called after a new
|
135
|
+
# IPN arrives. Paypal will verify that all the information we received are
|
136
|
+
# correct and will return a ok or a fail.
|
137
|
+
#
|
138
|
+
# Example:
|
139
|
+
#
|
140
|
+
# def paypal_ipn
|
141
|
+
# notify = PaypalNotification.new(request.raw_post)
|
142
|
+
#
|
143
|
+
# if notify.acknowledge
|
144
|
+
# ... process order ... if notify.complete?
|
145
|
+
# else
|
146
|
+
# ... log possible hacking attempt ...
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
def acknowledge
|
150
|
+
payload = raw
|
151
|
+
|
152
|
+
uri = URI.parse(self.ipn_url)
|
153
|
+
request_path = "#{uri.path}?cmd=_notify-validate"
|
154
|
+
|
155
|
+
request = Net::HTTP::Post.new(request_path)
|
156
|
+
request['Content-Length'] = "#{payload.size}"
|
157
|
+
request['User-Agent'] = "ruby-paypal-extended -- http://github.com/MicahWedemeyer/ruby-paypal-extended"
|
158
|
+
|
159
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
160
|
+
|
161
|
+
if uri.scheme == "https"
|
162
|
+
http.use_ssl = true
|
163
|
+
# http://www.ruby-lang.org/en/news/2007/10/04/net-https-vulnerability/
|
164
|
+
if http.respond_to?(:enable_post_connection_check)
|
165
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
166
|
+
http.enable_post_connection_check = true
|
167
|
+
store = OpenSSL::X509::Store.new
|
168
|
+
store.set_default_paths
|
169
|
+
http.cert_store = store
|
170
|
+
else
|
171
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
request = http.request(request, payload)
|
176
|
+
|
177
|
+
raise IPNException.new("Faulty paypal result: #{request.body}") unless ["VERIFIED", "INVALID"].include?(request.body)
|
178
|
+
|
179
|
+
request.body == "VERIFIED"
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
# Take the posted data and move the relevant data into a hash
|
185
|
+
def parse(post)
|
186
|
+
@raw = post
|
187
|
+
for line in post.split('&')
|
188
|
+
key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
|
189
|
+
@params[key] = CGI.unescape(value)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module PayPalSDK
|
2
|
+
module Operations
|
3
|
+
|
4
|
+
# Represents an arity mismatch where the number of receipients does
|
5
|
+
# not match all the other arguments
|
6
|
+
class MassPayArityException < PayPalException
|
7
|
+
end
|
8
|
+
|
9
|
+
# Represents the MassPay operation
|
10
|
+
class MassPay < Operation
|
11
|
+
attr_accessor :receiver_identifiers
|
12
|
+
attr_accessor :amounts
|
13
|
+
attr_accessor :receivertype
|
14
|
+
attr_accessor :currency_code
|
15
|
+
attr_accessor :email_subject
|
16
|
+
attr_accessor :unique_ids
|
17
|
+
attr_accessor :notes
|
18
|
+
|
19
|
+
# Creates a new MassPay operation.
|
20
|
+
#
|
21
|
+
# <tt>receiver_identifiers</tt> - An array of identifiers for the receivers, either
|
22
|
+
# email addresses or paypal IDs. As specified in the docs, they must all be
|
23
|
+
# one or the other.
|
24
|
+
# <tt>amounts</tt> - An array of numeric values representing the amounts to send to each recipient.
|
25
|
+
# <tt>receivertype</tt> - Must be either "UserID" or "EmailAddress". Defaults to EmailAddress
|
26
|
+
# <tt>currency_code</tt> - The 3 letter currency code
|
27
|
+
# <tt>email_subect</tt> - Subject of the email that will be sent to everyone
|
28
|
+
# <tt>unique_ids</tt> - An array of unique identifiers to attach to each transaction. Optional
|
29
|
+
# <tt>notes</tt> - An array of notes to attach to each transaction. Optional
|
30
|
+
def initialize(opts)
|
31
|
+
opts = {
|
32
|
+
:receivertype => "EmailAddress",
|
33
|
+
:currency_code => "USD"
|
34
|
+
}.merge(opts)
|
35
|
+
|
36
|
+
@receiver_identifiers = opts[:receiver_identifiers]
|
37
|
+
@amounts = opts[:amounts]
|
38
|
+
@receivertype = opts[:receivertype]
|
39
|
+
@currency_code = opts[:currency_code]
|
40
|
+
@email_subject = opts[:email_subject]
|
41
|
+
@unique_ids = opts[:unique_ids]
|
42
|
+
@notes = opts[:notes]
|
43
|
+
|
44
|
+
@n_recip = @receiver_identifiers.size
|
45
|
+
check_arity
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def call_hash
|
51
|
+
# Double-check the arity before making the hash
|
52
|
+
check_arity
|
53
|
+
|
54
|
+
h = {
|
55
|
+
:method => "MassPay",
|
56
|
+
:receivertype => @receivertype,
|
57
|
+
:currency_code => @currency_code,
|
58
|
+
:email_subject => @email_subject
|
59
|
+
}
|
60
|
+
|
61
|
+
id_key = @receivertype == "UserID" ? "l_receiverid" : "l_email"
|
62
|
+
|
63
|
+
@receiver_identifiers.each_index do |i|
|
64
|
+
h["#{id_key}#{i}".to_sym] = @receiver_identifiers[i]
|
65
|
+
h["l_amt#{i}".to_sym] = @amounts[i]
|
66
|
+
h["l_uniqueid#{i}".to_sym] = @unique_ids[i] if @unique_ids
|
67
|
+
h["l_note#{i}".to_sym] = @notes[i] if @notes
|
68
|
+
end
|
69
|
+
|
70
|
+
h
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_arity
|
74
|
+
# Sloppy...
|
75
|
+
bad_sizes = []
|
76
|
+
bad_sizes << "amounts has #{@amounts.size} values" if @amounts.size != @n_recip
|
77
|
+
bad_sizes << "unique_ids has #{@unique_ids.size} values" if !@unique_ids.nil? && (@unique_ids.size != @n_recip)
|
78
|
+
bad_sizes << "notes has #{@notes.size} values" if !@notes.nil? && (@notes.size != @n_recip)
|
79
|
+
|
80
|
+
unless bad_sizes.empty?
|
81
|
+
msg = "Arity mismatch: #{@n_recip} user identifiers, but "
|
82
|
+
msg += bad_sizes.join(" and")
|
83
|
+
raise MassPayArityException.new(msg)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PayPalSDK
|
2
|
+
module Operations
|
3
|
+
# Represents a base class method that is not yet implemented.
|
4
|
+
class NotImplementedException < PayPalException
|
5
|
+
end
|
6
|
+
|
7
|
+
# Abstract base class for all the operations
|
8
|
+
class Operation
|
9
|
+
|
10
|
+
# Executes the operation and returns the response wrapped in
|
11
|
+
# a Transaction
|
12
|
+
def call(caller)
|
13
|
+
caller.call(call_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# Translates this Operation into the Hash expected by Caller
|
19
|
+
# Every Operation Class must implement this method
|
20
|
+
def call_hash
|
21
|
+
raise NotImplementedException.new("Must implement call_hash")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module PayPalSDK
|
2
|
+
|
3
|
+
# This class holds a merchant's API crednetials and PayPal endpoint information
|
4
|
+
class Profile
|
5
|
+
|
6
|
+
attr_accessor :credentials
|
7
|
+
attr_accessor :endpoints
|
8
|
+
attr_accessor :client_info
|
9
|
+
attr_accessor :proxy_info
|
10
|
+
|
11
|
+
# Proxy information of the client environment.
|
12
|
+
DEFAULT_PROXY_INFO = {"USE_PROXY" => false, "ADDRESS" => nil, "PORT" => nil, "USER" => nil, "PASSWORD" => nil }
|
13
|
+
|
14
|
+
# Creates a new Profile, setting it with the options provided
|
15
|
+
# Options are:
|
16
|
+
# <tt>credentials</tt> - A hash of user credentials with keys of "USER", "PWD", and "SIGNATURE"
|
17
|
+
# <tt>use_production</tt> - Set to true to interact with the production server. Defaults to false
|
18
|
+
# <tt>proxy_info</tt> - A hash of proxy server info with keys of "USE_PROXY", "ADDRESS", "PORT", "USER", "PASSWORD"
|
19
|
+
def initialize(credentials, use_production = false, proxy_info = nil)
|
20
|
+
@credentials = credentials
|
21
|
+
@proxy_info = proxy_info || DEFAULT_PROXY_INFO
|
22
|
+
|
23
|
+
@endpoints = use_production ? PayPalSDK::Config::PRODUCTION_API_ENDPOINT : PayPalSDK::Config::SANDBOX_API_ENDPOINT
|
24
|
+
|
25
|
+
@client_info = {
|
26
|
+
"VERSION" => PayPalSDK::Config::CLIENT_VERSION,
|
27
|
+
"SOURCE" => PayPalSDK::Config::CLIENT_SOURCE
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def use_proxy?
|
32
|
+
@proxy_info["USE_PROXY"]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PayPalSDK
|
2
|
+
# Wrapper class to wrap response hash from PayPal as an object and to provide nice helper methods
|
3
|
+
class Transaction
|
4
|
+
def initialize(data)
|
5
|
+
@success = data["ACK"].to_s != "Failure"
|
6
|
+
@response = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def success?
|
10
|
+
@success
|
11
|
+
end
|
12
|
+
|
13
|
+
def response
|
14
|
+
@response
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
PAYPAL, INC.
|
2
|
+
|
3
|
+
SDK LICENSE
|
4
|
+
|
5
|
+
NOTICE TO USER: PayPal, Inc. is providing the Software and Documentation for use under the terms of this Agreement. Any use, reproduction, modification or distribution of the Software or Documentation, or any derivatives or portions hereof, constitutes your acceptance of this Agreement.
|
6
|
+
|
7
|
+
As used in this Agreement, "PayPal" means PayPal, Inc. "Software" means the software code accompanying this agreement. "Documentation" means the documents, specifications and all other items accompanying this Agreement other than the Software.
|
8
|
+
|
9
|
+
1. LICENSE GRANT Subject to the terms of this Agreement, PayPal hereby grants you a non-exclusive, worldwide, royalty free license to use, reproduce, prepare derivative works from, publicly display, publicly perform, distribute and sublicense the Software for any purpose, provided the copyright notice below appears in a conspicuous location within the source code of the distributed Software and this license is distributed in the supporting documentation of the Software you distribute. Furthermore, you must comply with all third party licenses in order to use the third party software contained in the Software.
|
10
|
+
|
11
|
+
Subject to the terms of this Agreement, PayPal hereby grants you a non-exclusive, worldwide, royalty free license to use, reproduce, publicly display, publicly perform, distribute and sublicense the Documentation for any purpose. You may not modify the Documentation.
|
12
|
+
|
13
|
+
No title to the intellectual property in the Software or Documentation is transferred to you under the terms of this Agreement. You do not acquire any rights to the Software or the Documentation except as expressly set forth in this Agreement.
|
14
|
+
|
15
|
+
If you choose to distribute the Software in a commercial product, you do so with the understanding that you agree to defend, indemnify and hold harmless PayPal and its suppliers against any losses, damages and costs arising from the claims, lawsuits or other legal actions arising out of such distribution. You may distribute the Software in object code form under your own license, provided that your license agreement:
|
16
|
+
|
17
|
+
(a) complies with the terms and conditions of this license agreement;
|
18
|
+
|
19
|
+
(b) effectively disclaims all warranties and conditions, express or implied, on behalf of PayPal;
|
20
|
+
|
21
|
+
(c) effectively excludes all liability for damages on behalf of PayPal;
|
22
|
+
|
23
|
+
(d) states that any provisions that differ from this Agreement are offered by you alone and not PayPal; and
|
24
|
+
|
25
|
+
(e) states that the Software is available from you or PayPal and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
|
26
|
+
|
27
|
+
2. DISCLAIMER OF WARRANTY
|
28
|
+
PAYPAL LICENSES THE SOFTWARE AND DOCUMENTATION TO YOU ONLY ON AN "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. PAYPAL MAKES NO WARRANTY THAT THE SOFTWARE OR DOCUMENTATION WILL BE ERROR-FREE. Each user of the Software or Documentation is solely responsible for determining the appropriateness of using and distributing the Software and Documentation and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs, or equipment, and unavailability or interruption of operations. Use of the Software and Documentation is made with the understanding that PayPal will not provide you with any technical or customer support or maintenance. Some states or jurisdictions do not allow the exclusion of implied warranties or limitations on how long an implied warranty may last, so the above limitations may not apply to you. To the extent permissible, any implied warranties are limited to ninety (90) days.
|
29
|
+
|
30
|
+
|
31
|
+
3. LIMITATION OF LIABILITY
|
32
|
+
PAYPAL AND ITS SUPPLIERS SHALL NOT BE LIABLE FOR LOSS OR DAMAGE ARISING OUT OF THIS AGREEMENT OR FROM THE USE OF THE SOFTWARE OR DOCUMENTATION. IN NO EVENT WILL PAYPAL OR ITS SUPPLIERS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, CONSEQUENTIAL, INCIDENTAL, OR SPECIAL DAMAGES INCLUDING LOST PROFITS, LOST SAVINGS, COSTS, FEES, OR EXPENSES OF ANY KIND ARISING OUT OF ANY PROVISION OF THIS AGREEMENT OR THE USE OR THE INABILITY TO USE THE SOFTWARE OR DOCUMENTATION, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT INCLUDING NEGLIGENCE OR OTHERWISE), EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. PAYPAL'S AGGREGATE LIABILITY AND THAT OF ITS SUPPLIERS UNDER OR IN CONNECTION WITH THIS AGREEMENT SHALL BE LIMITED TO THE AMOUNT PAID BY YOU FOR THE SOFTWARE AND DOCUMENTATION.
|
33
|
+
|
34
|
+
4. TRADEMARK USAGE
|
35
|
+
PayPal is a trademark PayPal, Inc. in the United States and other countries. Such trademarks may not be used to endorse or promote any product unless expressly permitted under separate agreement with PayPal.
|
36
|
+
|
37
|
+
5. TERM
|
38
|
+
Your rights under this Agreement shall terminate if you fail to comply with any of the material terms or conditions of this Agreement and do not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all your rights under this Agreement terminate, you agree to cease use and distribution of the Software and Documentation as soon as reasonably practicable.
|
39
|
+
|
40
|
+
6. GOVERNING LAW AND JURISDICTION. This Agreement is governed by the statutes and laws of the State of California, without regard to the conflicts of law principles thereof. If any part of this Agreement is found void and unenforceable, it will not affect the validity of the balance of the Agreement, which shall remain valid and enforceable according to its terms. Any dispute arising out of or related to this Agreement shall be brought in the courts of Santa Clara County, California, USA.
|
41
|
+
|
42
|
+
7. GENERAL
|
43
|
+
You acknowledge that you have read this Agreement, understand it, and that it is the complete and exclusive statement of your agreement with PayPal which supersedes any prior agreement, oral or written, between PayPal and you with respect to the licensing to you of the Software and Documentation. No variation of the terms of this Agreement will be enforceable against PayPal unless PayPal gives its express consent in writing signed by an authorized signatory of PayPal.
|
44
|
+
|
data/test/caller_test.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CallerTest < Test::Unit::TestCase
|
4
|
+
include PayPalSDK
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@p = Profile.new(creds)
|
8
|
+
@c = Caller.new(@p)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_request_post_data
|
12
|
+
post_data = @c.request_post_data(req_hash)
|
13
|
+
assert post_data.is_a?(String)
|
14
|
+
|
15
|
+
expected = "currencycode=USD&emailsubject=You+have+received+a+payment&l_amt0=5.00&l_receiverid0=12345&method=MassPay&receivertype=UserID&PWD=QFZCWN5HZM8VBG7Q&SIGNATURE=A.d9eRKfd1yVkRrtmMfCFLTqa6M9AyodL0SJkhYztxUi8W9pCXF6.4NI&USER=sdk-three_api1.sdk.com&SOURCE=ruby-paypal-extended&VERSION=56.0"
|
16
|
+
assert_equal expected, post_data
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def creds(opts = {})
|
22
|
+
{
|
23
|
+
"USER" => "sdk-three_api1.sdk.com",
|
24
|
+
"PWD" => "QFZCWN5HZM8VBG7Q",
|
25
|
+
"SIGNATURE" => "A.d9eRKfd1yVkRrtmMfCFLTqa6M9AyodL0SJkhYztxUi8W9pCXF6.4NI"
|
26
|
+
}.merge(opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
def req_hash(opts = {})
|
30
|
+
{
|
31
|
+
:method => "MassPay",
|
32
|
+
:emailsubject => "You have received a payment",
|
33
|
+
:currencycode => "USD",
|
34
|
+
:receivertype => "UserID",
|
35
|
+
:l_receiverid0 => "12345",
|
36
|
+
:l_amt0 => "5.00"
|
37
|
+
}.merge(opts)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MassPayTest < Test::Unit::TestCase
|
4
|
+
include PayPalSDK
|
5
|
+
include PayPalSDK::Operations
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@mp = MassPay.new(mass_pay_opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_constructor_wrong_arity
|
12
|
+
assert_raise MassPayArityException do
|
13
|
+
opts = mass_pay_opts(:unique_ids => ["a","b"]) # Missing a unique identifier
|
14
|
+
MassPay.new(opts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_call_hash
|
19
|
+
h = @mp.send(:call_hash)
|
20
|
+
assert_equal "MassPay", h[:method]
|
21
|
+
|
22
|
+
assert_equal "USD", h[:currency_code]
|
23
|
+
assert_equal "UserID", h[:receivertype]
|
24
|
+
|
25
|
+
assert_equal "1", h[:l_receiverid0]
|
26
|
+
assert_equal 1.00, h[:l_amt0]
|
27
|
+
assert_equal "a", h[:l_uniqueid0]
|
28
|
+
|
29
|
+
assert_equal "3", h[:l_receiverid2]
|
30
|
+
assert_equal 10.00, h[:l_amt2]
|
31
|
+
assert_equal "c", h[:l_uniqueid2]
|
32
|
+
|
33
|
+
assert_nil h[:l_email0]
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_call_hash_email
|
37
|
+
opts = mass_pay_opts(
|
38
|
+
:receiver_identifiers => ["a@b.com", "a@c.com", "a@d.com"],
|
39
|
+
:receivertype => "EmailAddress"
|
40
|
+
)
|
41
|
+
mp = MassPay.new(opts)
|
42
|
+
|
43
|
+
h = mp.send(:call_hash)
|
44
|
+
|
45
|
+
assert_equal "EmailAddress", h[:receivertype]
|
46
|
+
assert_equal "a@b.com", h[:l_email0]
|
47
|
+
assert_equal "a@d.com", h[:l_email2]
|
48
|
+
|
49
|
+
assert_nil h[:l_receiverid0]
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def mass_pay_opts(opts = {})
|
55
|
+
{
|
56
|
+
:receiver_identifiers => ["1", "2", "3"],
|
57
|
+
:receivertype => "UserID",
|
58
|
+
:amounts => [1.00, 5.00, 10.00],
|
59
|
+
:currency_code => "USD",
|
60
|
+
:unique_ids => ["a", "b", "c"]
|
61
|
+
}.merge(opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class OperationTest < Test::Unit::TestCase
|
2
|
+
include PayPalSDK::Operations
|
3
|
+
|
4
|
+
def setup
|
5
|
+
@mock_caller = flexmock(:caller)
|
6
|
+
|
7
|
+
# We'll use the MassPay operation
|
8
|
+
@op = MassPay.new(mass_pay_opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_call
|
12
|
+
vals = @op.send(:call_hash)
|
13
|
+
@mock_caller.should_receive(:call).with(vals).once.and_return(true)
|
14
|
+
|
15
|
+
@op.call(@mock_caller)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def mass_pay_opts(opts = {})
|
21
|
+
{
|
22
|
+
:receiver_identifiers => ["1", "2", "3"],
|
23
|
+
:receiver_type => "UserID",
|
24
|
+
:amounts => [1.00, 5.00, 10.00],
|
25
|
+
:currency_code => "USD",
|
26
|
+
:unique_ids => ["a", "b", "c"]
|
27
|
+
}.merge(opts)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ProfileTest < Test::Unit::TestCase
|
4
|
+
include PayPalSDK
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@p = Profile.new(creds)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_constructor
|
11
|
+
@p = Profile.new(
|
12
|
+
creds,
|
13
|
+
true,
|
14
|
+
{"USE_PROXY" => true, "ADDRESS" => "foo", "PORT" => 99, "USER" => nil, "PASSWORD" => nil }
|
15
|
+
)
|
16
|
+
|
17
|
+
assert @p.use_proxy?
|
18
|
+
assert_equal 99, @p.proxy_info["PORT"]
|
19
|
+
assert_equal "api-3t.paypal.com", @p.endpoints["SERVER"]
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def creds(opts = {})
|
25
|
+
{
|
26
|
+
"USER" => "sdk-three_api1.sdk.com",
|
27
|
+
"PWD" => "QFZCWN5HZM8VBG7Q",
|
28
|
+
"SIGNATURE" => "A.d9eRKfd1yVkRrtmMfCFLTqa6M9AyodL0SJkhYztxUi8W9pCXF6.4NI"
|
29
|
+
}.merge(opts)
|
30
|
+
end
|
31
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-paypal-extended
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 5
|
8
|
+
- 1
|
9
|
+
version: 0.5.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Micah Wedemeyer
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2009-05-10 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: ruby-paypal-extended is a Ruby library for interacting with PayPal via the NVP (Name Value Pair) REST interface.
|
22
|
+
email: micah@aisleten.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/ruby-paypal-extended/caller.rb
|
31
|
+
- lib/ruby-paypal-extended/ipn/notification.rb
|
32
|
+
- lib/ruby-paypal-extended/operations/mass_pay.rb
|
33
|
+
- lib/ruby-paypal-extended/operations/operation.rb
|
34
|
+
- lib/ruby-paypal-extended/paypal_exception.rb
|
35
|
+
- lib/ruby-paypal-extended/profile.rb
|
36
|
+
- lib/ruby-paypal-extended/transaction.rb
|
37
|
+
- lib/ruby-paypal-extended.rb
|
38
|
+
- licenses/LICENSE.txt
|
39
|
+
- Rakefile
|
40
|
+
- README
|
41
|
+
- test/caller_test.rb
|
42
|
+
- test/mass_pay_test.rb
|
43
|
+
- test/operation_test.rb
|
44
|
+
- test/profile_test.rb
|
45
|
+
- test/test_helper.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/MicahWedemeyer/ruby-paypal-extended
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.6
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: A library for interfacing with PayPal.
|
76
|
+
test_files: []
|
77
|
+
|