paypal-recurring 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +45 -0
- data/README.rdoc +128 -0
- data/Rakefile +5 -0
- data/docs/PP_NVPAPI_DeveloperGuide.pdf +0 -0
- data/docs/PP_WPP_IntegrationGuide.pdf +0 -0
- data/lib/paypal-recurring.rb +1 -0
- data/lib/paypal/recurring.rb +106 -0
- data/lib/paypal/recurring/base.rb +149 -0
- data/lib/paypal/recurring/cacert.pem +3987 -0
- data/lib/paypal/recurring/request.rb +143 -0
- data/lib/paypal/recurring/response.rb +26 -0
- data/lib/paypal/recurring/response/base.rb +74 -0
- data/lib/paypal/recurring/response/checkout.rb +11 -0
- data/lib/paypal/recurring/response/details.rb +26 -0
- data/lib/paypal/recurring/response/manage_profile.rb +12 -0
- data/lib/paypal/recurring/response/payment.rb +22 -0
- data/lib/paypal/recurring/response/profile.rb +69 -0
- data/lib/paypal/recurring/version.rb +10 -0
- data/paypal-recurring.gemspec +25 -0
- data/spec/fixtures/checkout/failure.yml +26 -0
- data/spec/fixtures/checkout/success.yml +26 -0
- data/spec/fixtures/create_profile/failure.yml +26 -0
- data/spec/fixtures/create_profile/success.yml +26 -0
- data/spec/fixtures/details/cancelled.yml +26 -0
- data/spec/fixtures/details/failure.yml +26 -0
- data/spec/fixtures/details/success.yml +26 -0
- data/spec/fixtures/payment/failure.yml +26 -0
- data/spec/fixtures/payment/success.yml +26 -0
- data/spec/fixtures/profile/cancel/failure.yml +26 -0
- data/spec/fixtures/profile/cancel/success.yml +26 -0
- data/spec/fixtures/profile/failure.yml +26 -0
- data/spec/fixtures/profile/reactivate/failure.yml +26 -0
- data/spec/fixtures/profile/reactivate/success.yml +26 -0
- data/spec/fixtures/profile/success.yml +26 -0
- data/spec/fixtures/profile/suspend/failure.yml +26 -0
- data/spec/fixtures/profile/suspend/success.yml +26 -0
- data/spec/paypal/recurring_spec.rb +87 -0
- data/spec/paypal/request_spec.rb +102 -0
- data/spec/paypal/response/checkout_details_spec.rb +51 -0
- data/spec/paypal/response/checkout_spec.rb +32 -0
- data/spec/paypal/response/create_recurring_profile_spec.rb +39 -0
- data/spec/paypal/response/manage_profile_spec.rb +62 -0
- data/spec/paypal/response/profile_spec.rb +41 -0
- data/spec/paypal/response/request_payment_spec.rb +35 -0
- data/spec/spec_helper.rb +24 -0
- metadata +187 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
class Request
|
4
|
+
METHODS = {
|
5
|
+
:checkout => "SetExpressCheckout",
|
6
|
+
:payment => "DoExpressCheckoutPayment",
|
7
|
+
:details => "GetExpressCheckoutDetails",
|
8
|
+
:create_profile => "CreateRecurringPaymentsProfile",
|
9
|
+
:profile => "GetRecurringPaymentsProfileDetails",
|
10
|
+
:manage_profile => "ManageRecurringPaymentsProfileStatus"
|
11
|
+
}
|
12
|
+
|
13
|
+
ACTIONS = {
|
14
|
+
:cancel => "Cancel",
|
15
|
+
:suspend => "Suspend",
|
16
|
+
:reactivate => "Reactivate"
|
17
|
+
}
|
18
|
+
|
19
|
+
PERIOD = {
|
20
|
+
:daily => "Day",
|
21
|
+
:monthly => "Month",
|
22
|
+
:yearly => "Year"
|
23
|
+
}
|
24
|
+
|
25
|
+
OUTSTANDING = {
|
26
|
+
:next_billing => "AddToNextBilling",
|
27
|
+
:no_auto => "NoAutoBill"
|
28
|
+
}
|
29
|
+
|
30
|
+
ATTRIBUTES = {
|
31
|
+
:action => "ACTION",
|
32
|
+
:amount => ["PAYMENTREQUEST_0_AMT", "AMT"],
|
33
|
+
:billing_type => "L_BILLINGTYPE0",
|
34
|
+
:cancel_url => "CANCELURL",
|
35
|
+
:currency => ["PAYMENTREQUEST_0_CURRENCYCODE", "CURRENCYCODE"],
|
36
|
+
:description => ["DESC", "PAYMENTREQUEST_0_DESC", "L_BILLINGAGREEMENTDESCRIPTION0"],
|
37
|
+
:email => "EMAIL",
|
38
|
+
:failed => "MAXFAILEDPAYMENTS",
|
39
|
+
:frequency => "BILLINGFREQUENCY",
|
40
|
+
:ipn_url => ["PAYMENTREQUEST_0_NOTIFYURL", "NOTIFYURL"],
|
41
|
+
:method => "METHOD",
|
42
|
+
:no_shipping => "NOSHIPPING",
|
43
|
+
:outstanding => "AUTOBILLOUTAMT",
|
44
|
+
:password => "PWD",
|
45
|
+
:payer_id => "PAYERID",
|
46
|
+
:payment_action => "PAYMENTREQUEST_0_PAYMENTACTION",
|
47
|
+
:period => "BILLINGPERIOD",
|
48
|
+
:profile_id => "PROFILEID",
|
49
|
+
:reference => "PROFILEREFERENCE",
|
50
|
+
:return_url => "RETURNURL",
|
51
|
+
:signature => "SIGNATURE",
|
52
|
+
:start_at => "PROFILESTARTDATE",
|
53
|
+
:token => "TOKEN",
|
54
|
+
:username => "USER",
|
55
|
+
:version => "VERSION",
|
56
|
+
}
|
57
|
+
|
58
|
+
CA_FILE = File.dirname(__FILE__) + "/cacert.pem"
|
59
|
+
|
60
|
+
# Do a POST request to PayPal API.
|
61
|
+
# The +method+ argument is the name of the API method you want to invoke.
|
62
|
+
# For instance, if you want to request a new checkout token, you may want
|
63
|
+
# to do something like:
|
64
|
+
#
|
65
|
+
# response = request.post(:express_checkout)
|
66
|
+
#
|
67
|
+
# We normalize the methods name. For a list of what's being covered, refer to
|
68
|
+
# PayPal::Recurring::Request::METHODS constant.
|
69
|
+
#
|
70
|
+
# The params hash can use normalized names. For a list, check the
|
71
|
+
# PayPal::Recurring::Request::ATTRIBUTES constant.
|
72
|
+
#
|
73
|
+
def post(method, params = {})
|
74
|
+
params = prepare_params(params.merge(:method => METHODS.fetch(method, method.to_s)))
|
75
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
76
|
+
request["User-Agent"] = "PayPal::Recurring/#{PayPal::Recurring::Version::STRING}"
|
77
|
+
request.form_data = params
|
78
|
+
response = client.request(request)
|
79
|
+
Response.process(method, response)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Join params and normalize attribute names.
|
83
|
+
#
|
84
|
+
def prepare_params(params) # :nodoc:
|
85
|
+
normalize_params default_params.merge(params)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Parse current API url.
|
89
|
+
#
|
90
|
+
def uri # :nodoc:
|
91
|
+
@uri ||= URI.parse(PayPal::Recurring.api_endpoint)
|
92
|
+
end
|
93
|
+
|
94
|
+
def client
|
95
|
+
@client ||= begin
|
96
|
+
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
97
|
+
http.use_ssl = true
|
98
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
99
|
+
http.ca_file = CA_FILE
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_params
|
105
|
+
{
|
106
|
+
:username => PayPal::Recurring.username,
|
107
|
+
:password => PayPal::Recurring.password,
|
108
|
+
:signature => PayPal::Recurring.signature,
|
109
|
+
:version => PayPal::Recurring.api_version
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def normalize_params(params)
|
114
|
+
params.inject({}) do |buffer, (name, value)|
|
115
|
+
attr_names = [ATTRIBUTES[name.to_sym]].flatten.compact
|
116
|
+
attr_names << name if attr_names.empty?
|
117
|
+
|
118
|
+
attr_names.each do |attr_name|
|
119
|
+
buffer[attr_name.to_sym] = respond_to?("build_#{name}") ? send("build_#{name}", value) : value
|
120
|
+
end
|
121
|
+
|
122
|
+
buffer
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def build_period(value) # :nodoc:
|
127
|
+
PERIOD.fetch(value.to_sym, value) if value
|
128
|
+
end
|
129
|
+
|
130
|
+
def build_start_at(value) # :nodoc:
|
131
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%dT%H:%M:%SZ") : value
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_outstanding(value) # :nodoc:
|
135
|
+
OUTSTANDING.fetch(value.to_sym, value) if value
|
136
|
+
end
|
137
|
+
|
138
|
+
def build_action(value) # :nodoc:
|
139
|
+
ACTIONS.fetch(value.to_sym, value) if value
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
autoload :Base, "paypal/recurring/response/base"
|
5
|
+
autoload :Checkout, "paypal/recurring/response/checkout"
|
6
|
+
autoload :Details, "paypal/recurring/response/details"
|
7
|
+
autoload :Payment, "paypal/recurring/response/payment"
|
8
|
+
autoload :ManageProfile, "paypal/recurring/response/manage_profile"
|
9
|
+
autoload :Profile, "paypal/recurring/response/profile"
|
10
|
+
|
11
|
+
RESPONDERS = {
|
12
|
+
:checkout => "Checkout",
|
13
|
+
:details => "Details",
|
14
|
+
:payment => "Payment",
|
15
|
+
:profile => "Profile",
|
16
|
+
:create_profile => "ManageProfile",
|
17
|
+
:manage_profile => "ManageProfile"
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.process(method, response)
|
21
|
+
response_class = PayPal::Recurring::Response.const_get(RESPONDERS[method])
|
22
|
+
response_class.new(response)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Base
|
5
|
+
attr_accessor :response
|
6
|
+
|
7
|
+
def self.mapping(options = {})
|
8
|
+
options.each do |to, from|
|
9
|
+
class_eval <<-RUBY
|
10
|
+
def #{to}
|
11
|
+
@#{to} ||= begin
|
12
|
+
value = params[:#{from}]
|
13
|
+
value = send("build_#{to}", params[:#{from}]) if respond_to?("build_#{to}", true)
|
14
|
+
value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
mapping(
|
22
|
+
:token => :TOKEN,
|
23
|
+
:ack => :ACK,
|
24
|
+
:version => :VERSION,
|
25
|
+
:build => :BUILD,
|
26
|
+
:correlaction_id => :CORRELATIONID,
|
27
|
+
:requested_at => :TIMESTAMP
|
28
|
+
)
|
29
|
+
|
30
|
+
def initialize(response = nil)
|
31
|
+
@response = response
|
32
|
+
end
|
33
|
+
|
34
|
+
def params
|
35
|
+
@params ||= CGI.parse(response.body).inject({}) do |buffer, (name, value)|
|
36
|
+
buffer.merge(name.to_sym => value.first)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def errors
|
41
|
+
@errors ||= begin
|
42
|
+
index = 0
|
43
|
+
[].tap do |errors|
|
44
|
+
while params[:"L_ERRORCODE#{index}"]
|
45
|
+
errors << {
|
46
|
+
:code => params[:"L_ERRORCODE#{index}"],
|
47
|
+
:messages => [
|
48
|
+
params[:"L_SHORTMESSAGE#{index}"],
|
49
|
+
params[:"L_LONGMESSAGE#{index}"]
|
50
|
+
]
|
51
|
+
}
|
52
|
+
|
53
|
+
index += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def success?
|
60
|
+
ack == "Success"
|
61
|
+
end
|
62
|
+
|
63
|
+
def valid?
|
64
|
+
errors.empty? && success?
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def build_requested_at(stamp) # :nodoc:
|
69
|
+
Time.parse(stamp)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Details < Base
|
5
|
+
mapping(
|
6
|
+
:status => :CHECKOUTSTATUS,
|
7
|
+
:email => :EMAIL,
|
8
|
+
:email => :EMAIL,
|
9
|
+
:payer_id => :PAYERID,
|
10
|
+
:payer_status => :PAYERSTATUS,
|
11
|
+
:first_name => :FIRSTNAME,
|
12
|
+
:last_name => :LASTNAME,
|
13
|
+
:country => :COUNTRYCODE,
|
14
|
+
:currency => :CURRENCYCODE,
|
15
|
+
:amount => :AMT,
|
16
|
+
:description => :DESC,
|
17
|
+
:ipn_url => :NOTIFYURL
|
18
|
+
)
|
19
|
+
|
20
|
+
def agreed?
|
21
|
+
params[:BILLINGAGREEMENTACCEPTEDSTATUS] == "1"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Payment < Base
|
5
|
+
mapping(
|
6
|
+
:status => :PAYMENTINFO_0_PAYMENTSTATUS,
|
7
|
+
:amount => :PAYMENTINFO_0_AMT,
|
8
|
+
:fees => :PAYMENTINFO_0_FEEAMT,
|
9
|
+
:seller_id => :PAYMENTINFO_0_SECUREMERCHANTACCOUNTID
|
10
|
+
)
|
11
|
+
|
12
|
+
def completed?
|
13
|
+
params[:PAYMENTINFO_0_PAYMENTSTATUS] == "Completed"
|
14
|
+
end
|
15
|
+
|
16
|
+
def approved?
|
17
|
+
params[:PAYMENTINFO_0_ACK] == "Success"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Profile < Base
|
5
|
+
mapping(
|
6
|
+
:profile_id => :PROFILEID,
|
7
|
+
:status => :STATUS,
|
8
|
+
:description => :DESC,
|
9
|
+
:outstanding => :AUTOBILLOUTAMT,
|
10
|
+
:failed => :MAXFAILEDPAYMENTS,
|
11
|
+
:payer_name => :SUBSCRIBERNAME,
|
12
|
+
:start_at => :PROFILESTARTDATE,
|
13
|
+
:reference => :PROFILEREFERENCE,
|
14
|
+
:completed => :NUMCYCLESCOMPLETED,
|
15
|
+
:remaining => :NUMCYCLESREMAINING,
|
16
|
+
:outstanding_balance => :OUTSTANDINGBALANCE,
|
17
|
+
:failed_count => :FAILEDPAYMENTCOUNT,
|
18
|
+
:last_payment_date => :LASTPAYMENTDATE,
|
19
|
+
:last_payment_amount => :LASTPAYMENTAMT,
|
20
|
+
:period => :BILLINGPERIOD,
|
21
|
+
:frequency => :BILLINGFREQUENCY,
|
22
|
+
:currency => :CURRENCYCODE,
|
23
|
+
:amount => :AMT
|
24
|
+
)
|
25
|
+
|
26
|
+
OUTSTANDING = {
|
27
|
+
"AddToNextBilling" => :next_billing,
|
28
|
+
"NoAutoBill" => :no_auto
|
29
|
+
}
|
30
|
+
|
31
|
+
STATUS = {
|
32
|
+
"Cancelled" => :canceled,
|
33
|
+
"Active" => :active,
|
34
|
+
"Suspended" => :suspended
|
35
|
+
}
|
36
|
+
|
37
|
+
PERIOD = {
|
38
|
+
"Month" => :monthly,
|
39
|
+
"Year" => :yearly,
|
40
|
+
"Day" => :daily
|
41
|
+
}
|
42
|
+
|
43
|
+
def active?
|
44
|
+
status == :active
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def build_outstanding(value)
|
49
|
+
OUTSTANDING.fetch(value, value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_status(value)
|
53
|
+
STATUS.fetch(value, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_date(string)
|
57
|
+
Time.parse(string)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_period(value)
|
61
|
+
PERIOD.fetch(value, value)
|
62
|
+
end
|
63
|
+
|
64
|
+
alias_method :build_start_at, :build_date
|
65
|
+
alias_method :build_last_payment_date, :build_date
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "paypal-recurring"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "paypal-recurring"
|
7
|
+
s.version = PayPal::Recurring::Version::STRING
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Nando Vieira"]
|
10
|
+
s.email = ["fnando.vieira@gmail.com"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/paypal-recurring"
|
12
|
+
s.summary = "PayPal Express Checkout API Client for recurring billing."
|
13
|
+
s.description = s.summary
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
21
|
+
s.add_development_dependency "rake", "~> 0.8.7"
|
22
|
+
s.add_development_dependency "vcr", "~> 1.10"
|
23
|
+
s.add_development_dependency "fakeweb", "~> 1.3.0"
|
24
|
+
s.add_development_dependency "ruby-debug19"
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
- !ruby/struct:VCR::HTTPInteraction
|
3
|
+
request: !ruby/struct:VCR::Request
|
4
|
+
method: :post
|
5
|
+
uri: https://api-3t.sandbox.paypal.com:443/nvp
|
6
|
+
body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PAYMENTREQUEST_0_PAYMENTACTION=Authorization&NOSHIPPING=1&L_BILLINGTYPE0=RecurringPayments&METHOD=SetExpressCheckout
|
7
|
+
headers:
|
8
|
+
user-agent:
|
9
|
+
- PayPal::Recurring/0.1.0
|
10
|
+
content-type:
|
11
|
+
- application/x-www-form-urlencoded
|
12
|
+
response: !ruby/struct:VCR::Response
|
13
|
+
status: !ruby/struct:VCR::ResponseStatus
|
14
|
+
code: 200
|
15
|
+
message: OK
|
16
|
+
headers:
|
17
|
+
date:
|
18
|
+
- Sun, 26 Jun 2011 23:40:00 GMT
|
19
|
+
server:
|
20
|
+
- Apache
|
21
|
+
content-length:
|
22
|
+
- "779"
|
23
|
+
content-type:
|
24
|
+
- text/plain; charset=utf-8
|
25
|
+
body: TIMESTAMP=2011%2d06%2d26T23%3a40%3a02Z&CORRELATIONID=4cd5b6afca9b2&ACK=Failure&VERSION=72%2e0&BUILD=1936884&L_ERRORCODE0=10478&L_ERRORCODE1=10404&L_ERRORCODE2=10405&L_SHORTMESSAGE0=Invalid%20Data&L_SHORTMESSAGE1=Transaction%20refused%20because%20of%20an%20invalid%20argument%2e%20See%20additional%20error%20messages%20for%20details%2e&L_SHORTMESSAGE2=Transaction%20refused%20because%20of%20an%20invalid%20argument%2e%20See%20additional%20error%20messages%20for%20details%2e&L_LONGMESSAGE0=Recurring%20payments%20profile%20description%20must%20be%20provided%20if%20the%20billing%20agreement%20type%20is%20recurring%20payments%2e&L_LONGMESSAGE1=ReturnURL%20is%20missing%2e&L_LONGMESSAGE2=CancelURL%20is%20missing%2e&L_SEVERITYCODE0=Error&L_SEVERITYCODE1=Error&L_SEVERITYCODE2=Error
|
26
|
+
http_version: "1.1"
|