paypal-recurring 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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"