paypal-recurring 0.1.0

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.
Files changed (49) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +45 -0
  5. data/README.rdoc +128 -0
  6. data/Rakefile +5 -0
  7. data/docs/PP_NVPAPI_DeveloperGuide.pdf +0 -0
  8. data/docs/PP_WPP_IntegrationGuide.pdf +0 -0
  9. data/lib/paypal-recurring.rb +1 -0
  10. data/lib/paypal/recurring.rb +106 -0
  11. data/lib/paypal/recurring/base.rb +149 -0
  12. data/lib/paypal/recurring/cacert.pem +3987 -0
  13. data/lib/paypal/recurring/request.rb +143 -0
  14. data/lib/paypal/recurring/response.rb +26 -0
  15. data/lib/paypal/recurring/response/base.rb +74 -0
  16. data/lib/paypal/recurring/response/checkout.rb +11 -0
  17. data/lib/paypal/recurring/response/details.rb +26 -0
  18. data/lib/paypal/recurring/response/manage_profile.rb +12 -0
  19. data/lib/paypal/recurring/response/payment.rb +22 -0
  20. data/lib/paypal/recurring/response/profile.rb +69 -0
  21. data/lib/paypal/recurring/version.rb +10 -0
  22. data/paypal-recurring.gemspec +25 -0
  23. data/spec/fixtures/checkout/failure.yml +26 -0
  24. data/spec/fixtures/checkout/success.yml +26 -0
  25. data/spec/fixtures/create_profile/failure.yml +26 -0
  26. data/spec/fixtures/create_profile/success.yml +26 -0
  27. data/spec/fixtures/details/cancelled.yml +26 -0
  28. data/spec/fixtures/details/failure.yml +26 -0
  29. data/spec/fixtures/details/success.yml +26 -0
  30. data/spec/fixtures/payment/failure.yml +26 -0
  31. data/spec/fixtures/payment/success.yml +26 -0
  32. data/spec/fixtures/profile/cancel/failure.yml +26 -0
  33. data/spec/fixtures/profile/cancel/success.yml +26 -0
  34. data/spec/fixtures/profile/failure.yml +26 -0
  35. data/spec/fixtures/profile/reactivate/failure.yml +26 -0
  36. data/spec/fixtures/profile/reactivate/success.yml +26 -0
  37. data/spec/fixtures/profile/success.yml +26 -0
  38. data/spec/fixtures/profile/suspend/failure.yml +26 -0
  39. data/spec/fixtures/profile/suspend/success.yml +26 -0
  40. data/spec/paypal/recurring_spec.rb +87 -0
  41. data/spec/paypal/request_spec.rb +102 -0
  42. data/spec/paypal/response/checkout_details_spec.rb +51 -0
  43. data/spec/paypal/response/checkout_spec.rb +32 -0
  44. data/spec/paypal/response/create_recurring_profile_spec.rb +39 -0
  45. data/spec/paypal/response/manage_profile_spec.rb +62 -0
  46. data/spec/paypal/response/profile_spec.rb +41 -0
  47. data/spec/paypal/response/request_payment_spec.rb +35 -0
  48. data/spec/spec_helper.rb +24 -0
  49. 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,11 @@
1
+ module PayPal
2
+ module Recurring
3
+ module Response
4
+ class Checkout < Base
5
+ def checkout_url
6
+ "#{PayPal::Recurring.site_endpoint}?cmd=_express-checkout&token=#{token}"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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,12 @@
1
+ module PayPal
2
+ module Recurring
3
+ module Response
4
+ class ManageProfile < Base
5
+ mapping(
6
+ :profile_id => :PROFILEID,
7
+ :status => :PROFILESTATUS
8
+ )
9
+ end
10
+ end
11
+ end
12
+ 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,10 @@
1
+ module PayPal
2
+ module Recurring
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
8
+ end
9
+ end
10
+ 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"