Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial release

  • Loading branch information...
commit 1316578de78787c93fc6231044f1879185ce9a67 0 parents
@fnando authored
Showing with 5,760 additions and 0 deletions.
  1. +3 −0  .gitignore
  2. +1 −0  .rspec
  3. +2 −0  Gemfile
  4. +45 −0 Gemfile.lock
  5. +128 −0 README.rdoc
  6. +5 −0 Rakefile
  7. BIN  docs/PP_NVPAPI_DeveloperGuide.pdf
  8. BIN  docs/PP_WPP_IntegrationGuide.pdf
  9. +1 −0  lib/paypal-recurring.rb
  10. +106 −0 lib/paypal/recurring.rb
  11. +149 −0 lib/paypal/recurring/base.rb
  12. +3,987 −0 lib/paypal/recurring/cacert.pem
  13. +143 −0 lib/paypal/recurring/request.rb
  14. +26 −0 lib/paypal/recurring/response.rb
  15. +74 −0 lib/paypal/recurring/response/base.rb
  16. +11 −0 lib/paypal/recurring/response/checkout.rb
  17. +26 −0 lib/paypal/recurring/response/details.rb
  18. +12 −0 lib/paypal/recurring/response/manage_profile.rb
  19. +22 −0 lib/paypal/recurring/response/payment.rb
  20. +69 −0 lib/paypal/recurring/response/profile.rb
  21. +10 −0 lib/paypal/recurring/version.rb
  22. +25 −0 paypal-recurring.gemspec
  23. +26 −0 spec/fixtures/checkout/failure.yml
  24. +26 −0 spec/fixtures/checkout/success.yml
  25. +26 −0 spec/fixtures/create_profile/failure.yml
  26. +26 −0 spec/fixtures/create_profile/success.yml
  27. +26 −0 spec/fixtures/details/cancelled.yml
  28. +26 −0 spec/fixtures/details/failure.yml
  29. +26 −0 spec/fixtures/details/success.yml
  30. +26 −0 spec/fixtures/payment/failure.yml
  31. +26 −0 spec/fixtures/payment/success.yml
  32. +26 −0 spec/fixtures/profile/cancel/failure.yml
  33. +26 −0 spec/fixtures/profile/cancel/success.yml
  34. +26 −0 spec/fixtures/profile/failure.yml
  35. +26 −0 spec/fixtures/profile/reactivate/failure.yml
  36. +26 −0 spec/fixtures/profile/reactivate/success.yml
  37. +26 −0 spec/fixtures/profile/success.yml
  38. +26 −0 spec/fixtures/profile/suspend/failure.yml
  39. +26 −0 spec/fixtures/profile/suspend/success.yml
  40. +87 −0 spec/paypal/recurring_spec.rb
  41. +102 −0 spec/paypal/request_spec.rb
  42. +51 −0 spec/paypal/response/checkout_details_spec.rb
  43. +32 −0 spec/paypal/response/checkout_spec.rb
  44. +39 −0 spec/paypal/response/create_recurring_profile_spec.rb
  45. +62 −0 spec/paypal/response/manage_profile_spec.rb
  46. +41 −0 spec/paypal/response/profile_spec.rb
  47. +35 −0 spec/paypal/response/request_payment_spec.rb
  48. +24 −0 spec/spec_helper.rb
3  .gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+pkg
+tmp
1  .rspec
@@ -0,0 +1 @@
+--color --format documentation
2  Gemfile
@@ -0,0 +1,2 @@
+source :rubygems
+gemspec
45 Gemfile.lock
@@ -0,0 +1,45 @@
+PATH
+ remote: .
+ specs:
+ paypal-recurring (0.1.0)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ archive-tar-minitar (0.5.2)
+ columnize (0.3.3)
+ diff-lcs (1.1.2)
+ fakeweb (1.3.0)
+ linecache19 (0.5.12)
+ ruby_core_source (>= 0.1.4)
+ rake (0.8.7)
+ rspec (2.6.0)
+ rspec-core (~> 2.6.0)
+ rspec-expectations (~> 2.6.0)
+ rspec-mocks (~> 2.6.0)
+ rspec-core (2.6.4)
+ rspec-expectations (2.6.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.6.0)
+ ruby-debug-base19 (0.11.25)
+ columnize (>= 0.3.1)
+ linecache19 (>= 0.5.11)
+ ruby_core_source (>= 0.1.4)
+ ruby-debug19 (0.11.6)
+ columnize (>= 0.3.1)
+ linecache19 (>= 0.5.11)
+ ruby-debug-base19 (>= 0.11.19)
+ ruby_core_source (0.1.5)
+ archive-tar-minitar (>= 0.5.2)
+ vcr (1.10.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ fakeweb (~> 1.3.0)
+ paypal-recurring!
+ rake (~> 0.8.7)
+ rspec (~> 2.6)
+ ruby-debug19
+ vcr (~> 1.10)
128 README.rdoc
@@ -0,0 +1,128 @@
+= PayPal Recurring Billing
+
+PayPal Express Checkout API Client for recurring billing.
+
+== Installation
+
+ gem install paypal-recurring
+
+== Usage
+
+First, you need to set up your credentials:
+
+ require "paypal/recurring"
+
+ PayPal::Recurring.configure do |config|
+ config.sandbox = true
+ config.username = "seller_1308793919_biz_api1.simplesideias.com.br"
+ config.password = "1308793931"
+ config.signature = "AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y"
+ end
+
+Then, you can request a new payment authorization:
+
+ ppr = PayPal::Recurring.new({
+ :return_url => "http://example.com/paypal/thank_you",
+ :cancel_url => "http://example.com/paypal/canceled",
+ :ipn_url => "http://example.com/paypal/ipn",
+ :description => "Awesome - Monthly Subscription",
+ :amount => "9.00",
+ :currency => "USD"
+ })
+
+ response = ppr.checkout
+ puts response.checkout_url if response.valid?
+
+You need to redirect your user to the url returned by <tt>response.checkout_url</tt>.
+After the user accepts or rejects your payment request, he will be redirected to one of those urls you specified.
+The return url will receive two parameters: <tt>PAYERID</tt> and <tt>TOKEN</tt>. You can use the <tt>TOKEN</tt>
+parameter to identify your user on your database.
+
+If you need to retrieve information about your buyer, like address or e-mail, you can use the
+<tt>checkout_details()</tt> method.
+
+ ppr = PayPal::Recurring.new(:token => "EC-05C46042TU8306821")
+ response = ppr.checkout_details
+
+Now, you need to request payment. The information you provide here should be exactly the same when you started
+the checkout process.
+
+ ppr = PayPal::Recurring.new({
+ :token => "EC-05C46042TU8306821",
+ :payer_id => "WTTS5KC2T46YU",
+ :amount => "9.00",
+ :description => "Awesome - Monthly Subscription"
+ })
+ response = ppr.request_payment
+ response.approved?
+ response.completed?
+
+Finally, you need to create a new recurring profile.
+
+ ppr = PayPal::Recurring.new({
+ :amount => "9.00",
+ :currency => "USD",
+ :description => "Awesome - Monthly Subscription",
+ :ipn_url => "http://example.com/paypal/ipn",
+ :frequency => 1,
+ :token => "EC-05C46042TU8306821",
+ :period => :monthly,
+ :reference => "1234",
+ :payer_id => "WTTS5KC2T46YU",
+ :start_at => Time.now,
+ :failed => 1,
+ :outstanding => :next_billing
+ })
+
+ response = ppr.create_recurring_profile
+ puts response.profile_id
+
+You can manage your recurring profile.
+
+ ppr = PayPal::Recurring.new(:profile_id => "I-VCEL6TRG35CU")
+
+ ppr.suspend
+ ppr.reactivate
+ ppr.cancel
+
+=== What information do I need to keep?
+
+You should save two paramaters to your database: <tt>TOKEN</tt> and <tt>PROFILEID</tt>.
+<tt>TOKEN</tt> is required when user returns to your website after he authorizes (or not) the billing process. You
+need to save it so you can find him later. You can remove this info after payment and recurring profile are set.
+
+The <tt>PROFILEID</tt> allows you to manage the recurring profile, like canceling billing when an user don't
+want to use your service anymore.
+
+<b>NOTE:</b> TOKEN will expire after approximately 3 hours.
+
+== TO-DO
+
+* handle Instant Payment Notifications (IPN)
+
+== Maintainer
+
+* Nando Vieira (http://nandovieira.com.br)
+
+== License
+
+(The MIT License)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5 Rakefile
@@ -0,0 +1,5 @@
+require "bundler"
+Bundler::GemHelper.install_tasks
+
+require "rspec/core/rake_task"
+RSpec::Core::RakeTask.new
BIN  docs/PP_NVPAPI_DeveloperGuide.pdf
Binary file not shown
BIN  docs/PP_WPP_IntegrationGuide.pdf
Binary file not shown
1  lib/paypal-recurring.rb
@@ -0,0 +1 @@
+require "paypal/recurring"
106 lib/paypal/recurring.rb
@@ -0,0 +1,106 @@
+require "net/https"
+require "cgi"
+require "uri"
+require "logger"
+require "ostruct"
+require "time"
+
+module PayPal
+ module Recurring
+ autoload :Base, "paypal/recurring/base"
+ autoload :Request, "paypal/recurring/request"
+ autoload :Response, "paypal/recurring/response"
+ autoload :Version, "paypal/recurring/version"
+
+ ENDPOINTS = {
+ :sandbox => {
+ :api => "https://api-3t.sandbox.paypal.com/nvp",
+ :site => "https://www.sandbox.paypal.com/cgi-bin/webscr"
+ },
+ :production => {
+ :api => "https://api-3t.paypal.com/nvp",
+ :site => "https://www.paypal.com/cgi-bin/webscr"
+ }
+ }
+
+ class << self
+ # Define if requests should be made to PayPal's
+ # sandbox environment. This is specially useful when running
+ # on development or test mode.
+ #
+ # PayPal::Recurring.sandbox = true
+ #
+ attr_accessor :sandbox
+
+ # Set PayPal's API username.
+ #
+ attr_accessor :username
+
+ # Set PayPal's API password.
+ #
+ attr_accessor :password
+
+ # Set PayPal's API signature.
+ #
+ attr_accessor :signature
+
+ # Set application logger. By default, will send output to +STDOUT+.
+ #
+ attr_accessor :logger
+ end
+
+ self.logger = Logger.new(STDOUT)
+
+ # Just a shortcut for <tt>PayPal::Recurring::Base.new</tt>.
+ #
+ def self.new(options = {})
+ Base.new(options)
+ end
+
+ # Configure PayPal::Recurring options.
+ #
+ # PayPal::Recurring.configure do |config|
+ # config.sandbox = true
+ # end
+ #
+ def self.configure(&block)
+ yield PayPal::Recurring
+ end
+
+ # Detect if sandbox mode is enabled.
+ #
+ def self.sandbox?
+ sandbox == true
+ end
+
+ # Return a name for current environment mode (sandbox or production).
+ #
+ def self.environment
+ sandbox? ? :sandbox : :production
+ end
+
+ # Return URL endpoints for current environment.
+ #
+ def self.endpoints
+ ENDPOINTS[environment]
+ end
+
+ # Return API endpoint based on current environment.
+ #
+ def self.api_endpoint
+ endpoints[:api]
+ end
+
+ # Return PayPal's API version.
+ #
+ def self.api_version
+ "72.0"
+ end
+
+ # Return site endpoint based on current environment.
+ #
+ def self.site_endpoint
+ endpoints[:site]
+ end
+ end
+end
149 lib/paypal/recurring/base.rb
@@ -0,0 +1,149 @@
+module PayPal
+ module Recurring
+ class Base
+ attr_accessor :amount
+ attr_accessor :cancel_url
+ attr_accessor :currency
+ attr_accessor :description
+ attr_accessor :failed
+ attr_accessor :frequency
+ attr_accessor :ipn_url
+ attr_accessor :outstanding
+ attr_accessor :payer_id
+ attr_accessor :period
+ attr_accessor :profile_id
+ attr_accessor :reference
+ attr_accessor :return_url
+ attr_accessor :start_at
+ attr_accessor :token
+ attr_accessor :email
+
+ def initialize(options = {})
+ options.each {|name, value| send("#{name}=", value)}
+ end
+
+ # Just a shortcut convenience.
+ #
+ def request # :nodoc:
+ @request ||= Request.new
+ end
+
+ # Request a checkout token.
+ #
+ # ppr = PayPal::Recurring.new({
+ # :return_url => "http://example.com/checkout/thank_you",
+ # :cancel_url => "http://example.com/checkout/canceled",
+ # :ipn_url => "http://example.com/paypal/ipn",
+ # :description => "Awesome - Monthly Subscription",
+ # :amount => "9.00",
+ # :currency => "USD"
+ # })
+ #
+ # response = ppr.request_token
+ # response.checkout_url
+ #
+ def checkout
+ params = collect(:amount, :return_url, :cancel_url, :currency, :description, :ipn_url).merge(:payment_action => "Authorization", :no_shipping => 1, :L_BILLINGTYPE0 => "RecurringPayments")
+ request.post(:checkout, params)
+ end
+
+ # Suspend a recurring profile.
+ # Suspended profiles can be reactivated.
+ #
+ # ppr = PayPal::Recurring.new(:profile_id => "I-HYRKXBMNLFSK")
+ # response = ppr.suspend
+ #
+ def suspend
+ request.post(:manage_profile, :action => :suspend, :profile_id => profile_id)
+ end
+
+ # Reactivate a suspended recurring profile.
+ #
+ # ppr = PayPal::Recurring.new(:profile_id => "I-HYRKXBMNLFSK")
+ # response = ppr.reactivate
+ #
+ def reactivate
+ request.post(:manage_profile, :action => :reactivate, :profile_id => profile_id)
+ end
+
+ # Cancel a recurring profile.
+ # Cancelled profiles cannot be reactivated.
+ #
+ # ppr = PayPal::Recurring.new(:profile_id => "I-HYRKXBMNLFSK")
+ # response = ppr.cancel
+ #
+ def cancel
+ request.post(:manage_profile, :action => :cancel, :profile_id => profile_id)
+ end
+
+ # Return checkout details.
+ #
+ # ppr = PayPal::Recurring.new(:token => "EC-6LX60229XS426623E")
+ # response = ppr.checkout_details
+ #
+ def checkout_details
+ request.post(:details, :token => token)
+ end
+
+ # Request payment.
+ #
+ # # ppr = PayPal::Recurring.new({
+ # :token => "EC-6LX60229XS426623E",
+ # :payer_id => "WTTS5KC2T46YU",
+ # :amount => "9.00",
+ # :description => "Awesome - Monthly Subscription"
+ # })
+ # response = ppr.request_payment
+ # response.completed? && response.approved?
+ #
+ def request_payment
+ params = collect(:amount, :return_url, :cancel_url, :ipn_url, :currency, :description, :payer_id, :token).merge(:payment_action => "Sale")
+ request.post(:payment, params)
+ end
+
+ # Create a recurring billing profile.
+ #
+ # ppr = PayPal::Recurring.new({
+ # :amount => "9.00",
+ # :currency => "USD",
+ # :description => "Awesome - Monthly Subscription",
+ # :ipn_url => "http://example.com/paypal/ipn",
+ # :frequency => 1,
+ # :token => "EC-05C46042TU8306821",
+ # :period => :monthly,
+ # :reference => "1234",
+ # :payer_id => "WTTS5KC2T46YU",
+ # :start_at => Time.now,
+ # :failed => 1,
+ # :outstanding => :next_billing
+ # })
+ #
+ # response = ppr.create_recurring_profile
+ #
+ def create_recurring_profile
+ params = collect(:amount, :currency, :description, :payer_id, :token, :reference, :start_at, :failed, :outstanding, :ipn_url, :frequency, :period, :email)
+ request.post(:create_profile, params)
+ end
+
+ # Retrieve information about existing recurring profile.
+ #
+ # ppr = PayPal::Recurring.new(:profile_id => "I-VCEL6TRG35CU")
+ # response = ppr.profile
+ #
+ def profile
+ request.post(:profile, :profile_id => profile_id)
+ end
+
+ private
+ # Collect specified attributes and build a hash out of it.
+ #
+ def collect(*args) # :nodoc:
+ args.inject({}) do |buffer, attr_name|
+ value = send(attr_name)
+ buffer[attr_name] = value if value
+ buffer
+ end
+ end
+ end
+ end
+end
3,987 lib/paypal/recurring/cacert.pem
3,987 additions, 0 deletions not shown
143 lib/paypal/recurring/request.rb
@@ -0,0 +1,143 @@
+module PayPal
+ module Recurring
+ class Request
+ METHODS = {
+ :checkout => "SetExpressCheckout",
+ :payment => "DoExpressCheckoutPayment",
+ :details => "GetExpressCheckoutDetails",
+ :create_profile => "CreateRecurringPaymentsProfile",
+ :profile => "GetRecurringPaymentsProfileDetails",
+ :manage_profile => "ManageRecurringPaymentsProfileStatus"
+ }
+
+ ACTIONS = {
+ :cancel => "Cancel",
+ :suspend => "Suspend",
+ :reactivate => "Reactivate"
+ }
+
+ PERIOD = {
+ :daily => "Day",
+ :monthly => "Month",
+ :yearly => "Year"
+ }
+
+ OUTSTANDING = {
+ :next_billing => "AddToNextBilling",
+ :no_auto => "NoAutoBill"
+ }
+
+ ATTRIBUTES = {
+ :action => "ACTION",
+ :amount => ["PAYMENTREQUEST_0_AMT", "AMT"],
+ :billing_type => "L_BILLINGTYPE0",
+ :cancel_url => "CANCELURL",
+ :currency => ["PAYMENTREQUEST_0_CURRENCYCODE", "CURRENCYCODE"],
+ :description => ["DESC", "PAYMENTREQUEST_0_DESC", "L_BILLINGAGREEMENTDESCRIPTION0"],
+ :email => "EMAIL",
+ :failed => "MAXFAILEDPAYMENTS",
+ :frequency => "BILLINGFREQUENCY",
+ :ipn_url => ["PAYMENTREQUEST_0_NOTIFYURL", "NOTIFYURL"],
+ :method => "METHOD",
+ :no_shipping => "NOSHIPPING",
+ :outstanding => "AUTOBILLOUTAMT",
+ :password => "PWD",
+ :payer_id => "PAYERID",
+ :payment_action => "PAYMENTREQUEST_0_PAYMENTACTION",
+ :period => "BILLINGPERIOD",
+ :profile_id => "PROFILEID",
+ :reference => "PROFILEREFERENCE",
+ :return_url => "RETURNURL",
+ :signature => "SIGNATURE",
+ :start_at => "PROFILESTARTDATE",
+ :token => "TOKEN",
+ :username => "USER",
+ :version => "VERSION",
+ }
+
+ CA_FILE = File.dirname(__FILE__) + "/cacert.pem"
+
+ # Do a POST request to PayPal API.
+ # The +method+ argument is the name of the API method you want to invoke.
+ # For instance, if you want to request a new checkout token, you may want
+ # to do something like:
+ #
+ # response = request.post(:express_checkout)
+ #
+ # We normalize the methods name. For a list of what's being covered, refer to
+ # PayPal::Recurring::Request::METHODS constant.
+ #
+ # The params hash can use normalized names. For a list, check the
+ # PayPal::Recurring::Request::ATTRIBUTES constant.
+ #
+ def post(method, params = {})
+ params = prepare_params(params.merge(:method => METHODS.fetch(method, method.to_s)))
+ request = Net::HTTP::Post.new(uri.request_uri)
+ request["User-Agent"] = "PayPal::Recurring/#{PayPal::Recurring::Version::STRING}"
+ request.form_data = params
+ response = client.request(request)
+ Response.process(method, response)
+ end
+
+ # Join params and normalize attribute names.
+ #
+ def prepare_params(params) # :nodoc:
+ normalize_params default_params.merge(params)
+ end
+
+ # Parse current API url.
+ #
+ def uri # :nodoc:
+ @uri ||= URI.parse(PayPal::Recurring.api_endpoint)
+ end
+
+ def client
+ @client ||= begin
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ http.ca_file = CA_FILE
+ end
+ end
+ end
+
+ def default_params
+ {
+ :username => PayPal::Recurring.username,
+ :password => PayPal::Recurring.password,
+ :signature => PayPal::Recurring.signature,
+ :version => PayPal::Recurring.api_version
+ }
+ end
+
+ def normalize_params(params)
+ params.inject({}) do |buffer, (name, value)|
+ attr_names = [ATTRIBUTES[name.to_sym]].flatten.compact
+ attr_names << name if attr_names.empty?
+
+ attr_names.each do |attr_name|
+ buffer[attr_name.to_sym] = respond_to?("build_#{name}") ? send("build_#{name}", value) : value
+ end
+
+ buffer
+ end
+ end
+
+ def build_period(value) # :nodoc:
+ PERIOD.fetch(value.to_sym, value) if value
+ end
+
+ def build_start_at(value) # :nodoc:
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%dT%H:%M:%SZ") : value
+ end
+
+ def build_outstanding(value) # :nodoc:
+ OUTSTANDING.fetch(value.to_sym, value) if value
+ end
+
+ def build_action(value) # :nodoc:
+ ACTIONS.fetch(value.to_sym, value) if value
+ end
+ end
+ end
+end
26 lib/paypal/recurring/response.rb
@@ -0,0 +1,26 @@
+module PayPal
+ module Recurring
+ module Response
+ autoload :Base, "paypal/recurring/response/base"
+ autoload :Checkout, "paypal/recurring/response/checkout"
+ autoload :Details, "paypal/recurring/response/details"
+ autoload :Payment, "paypal/recurring/response/payment"
+ autoload :ManageProfile, "paypal/recurring/response/manage_profile"
+ autoload :Profile, "paypal/recurring/response/profile"
+
+ RESPONDERS = {
+ :checkout => "Checkout",
+ :details => "Details",
+ :payment => "Payment",
+ :profile => "Profile",
+ :create_profile => "ManageProfile",
+ :manage_profile => "ManageProfile"
+ }
+
+ def self.process(method, response)
+ response_class = PayPal::Recurring::Response.const_get(RESPONDERS[method])
+ response_class.new(response)
+ end
+ end
+ end
+end
74 lib/paypal/recurring/response/base.rb
@@ -0,0 +1,74 @@
+module PayPal
+ module Recurring
+ module Response
+ class Base
+ attr_accessor :response
+
+ def self.mapping(options = {})
+ options.each do |to, from|
+ class_eval <<-RUBY
+ def #{to}
+ @#{to} ||= begin
+ value = params[:#{from}]
+ value = send("build_#{to}", params[:#{from}]) if respond_to?("build_#{to}", true)
+ value
+ end
+ end
+ RUBY
+ end
+ end
+
+ mapping(
+ :token => :TOKEN,
+ :ack => :ACK,
+ :version => :VERSION,
+ :build => :BUILD,
+ :correlaction_id => :CORRELATIONID,
+ :requested_at => :TIMESTAMP
+ )
+
+ def initialize(response = nil)
+ @response = response
+ end
+
+ def params
+ @params ||= CGI.parse(response.body).inject({}) do |buffer, (name, value)|
+ buffer.merge(name.to_sym => value.first)
+ end
+ end
+
+ def errors
+ @errors ||= begin
+ index = 0
+ [].tap do |errors|
+ while params[:"L_ERRORCODE#{index}"]
+ errors << {
+ :code => params[:"L_ERRORCODE#{index}"],
+ :messages => [
+ params[:"L_SHORTMESSAGE#{index}"],
+ params[:"L_LONGMESSAGE#{index}"]
+ ]
+ }
+
+ index += 1
+ end
+ end
+ end
+ end
+
+ def success?
+ ack == "Success"
+ end
+
+ def valid?
+ errors.empty? && success?
+ end
+
+ private
+ def build_requested_at(stamp) # :nodoc:
+ Time.parse(stamp)
+ end
+ end
+ end
+ end
+end
11 lib/paypal/recurring/response/checkout.rb
@@ -0,0 +1,11 @@
+module PayPal
+ module Recurring
+ module Response
+ class Checkout < Base
+ def checkout_url
+ "#{PayPal::Recurring.site_endpoint}?cmd=_express-checkout&token=#{token}"
+ end
+ end
+ end
+ end
+end
26 lib/paypal/recurring/response/details.rb
@@ -0,0 +1,26 @@
+module PayPal
+ module Recurring
+ module Response
+ class Details < Base
+ mapping(
+ :status => :CHECKOUTSTATUS,
+ :email => :EMAIL,
+ :email => :EMAIL,
+ :payer_id => :PAYERID,
+ :payer_status => :PAYERSTATUS,
+ :first_name => :FIRSTNAME,
+ :last_name => :LASTNAME,
+ :country => :COUNTRYCODE,
+ :currency => :CURRENCYCODE,
+ :amount => :AMT,
+ :description => :DESC,
+ :ipn_url => :NOTIFYURL
+ )
+
+ def agreed?
+ params[:BILLINGAGREEMENTACCEPTEDSTATUS] == "1"
+ end
+ end
+ end
+ end
+end
12 lib/paypal/recurring/response/manage_profile.rb
@@ -0,0 +1,12 @@
+module PayPal
+ module Recurring
+ module Response
+ class ManageProfile < Base
+ mapping(
+ :profile_id => :PROFILEID,
+ :status => :PROFILESTATUS
+ )
+ end
+ end
+ end
+end
22 lib/paypal/recurring/response/payment.rb
@@ -0,0 +1,22 @@
+module PayPal
+ module Recurring
+ module Response
+ class Payment < Base
+ mapping(
+ :status => :PAYMENTINFO_0_PAYMENTSTATUS,
+ :amount => :PAYMENTINFO_0_AMT,
+ :fees => :PAYMENTINFO_0_FEEAMT,
+ :seller_id => :PAYMENTINFO_0_SECUREMERCHANTACCOUNTID
+ )
+
+ def completed?
+ params[:PAYMENTINFO_0_PAYMENTSTATUS] == "Completed"
+ end
+
+ def approved?
+ params[:PAYMENTINFO_0_ACK] == "Success"
+ end
+ end
+ end
+ end
+end
69 lib/paypal/recurring/response/profile.rb
@@ -0,0 +1,69 @@
+module PayPal
+ module Recurring
+ module Response
+ class Profile < Base
+ mapping(
+ :profile_id => :PROFILEID,
+ :status => :STATUS,
+ :description => :DESC,
+ :outstanding => :AUTOBILLOUTAMT,
+ :failed => :MAXFAILEDPAYMENTS,
+ :payer_name => :SUBSCRIBERNAME,
+ :start_at => :PROFILESTARTDATE,
+ :reference => :PROFILEREFERENCE,
+ :completed => :NUMCYCLESCOMPLETED,
+ :remaining => :NUMCYCLESREMAINING,
+ :outstanding_balance => :OUTSTANDINGBALANCE,
+ :failed_count => :FAILEDPAYMENTCOUNT,
+ :last_payment_date => :LASTPAYMENTDATE,
+ :last_payment_amount => :LASTPAYMENTAMT,
+ :period => :BILLINGPERIOD,
+ :frequency => :BILLINGFREQUENCY,
+ :currency => :CURRENCYCODE,
+ :amount => :AMT
+ )
+
+ OUTSTANDING = {
+ "AddToNextBilling" => :next_billing,
+ "NoAutoBill" => :no_auto
+ }
+
+ STATUS = {
+ "Cancelled" => :canceled,
+ "Active" => :active,
+ "Suspended" => :suspended
+ }
+
+ PERIOD = {
+ "Month" => :monthly,
+ "Year" => :yearly,
+ "Day" => :daily
+ }
+
+ def active?
+ status == :active
+ end
+
+ private
+ def build_outstanding(value)
+ OUTSTANDING.fetch(value, value)
+ end
+
+ def build_status(value)
+ STATUS.fetch(value, value)
+ end
+
+ def build_date(string)
+ Time.parse(string)
+ end
+
+ def build_period(value)
+ PERIOD.fetch(value, value)
+ end
+
+ alias_method :build_start_at, :build_date
+ alias_method :build_last_payment_date, :build_date
+ end
+ end
+ end
+end
10 lib/paypal/recurring/version.rb
@@ -0,0 +1,10 @@
+module PayPal
+ module Recurring
+ module Version
+ MAJOR = 0
+ MINOR = 1
+ PATCH = 0
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
+ end
+ end
+end
25 paypal-recurring.gemspec
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "paypal-recurring"
+
+Gem::Specification.new do |s|
+ s.name = "paypal-recurring"
+ s.version = PayPal::Recurring::Version::STRING
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Nando Vieira"]
+ s.email = ["fnando.vieira@gmail.com"]
+ s.homepage = "http://rubygems.org/gems/paypal-recurring"
+ s.summary = "PayPal Express Checkout API Client for recurring billing."
+ s.description = s.summary
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_development_dependency "rspec", "~> 2.6"
+ s.add_development_dependency "rake", "~> 0.8.7"
+ s.add_development_dependency "vcr", "~> 1.10"
+ s.add_development_dependency "fakeweb", "~> 1.3.0"
+ s.add_development_dependency "ruby-debug19"
+end
26 spec/fixtures/checkout/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ 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
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Sun, 26 Jun 2011 23:40:00 GMT
+ server:
+ - Apache
+ content-length:
+ - "779"
+ content-type:
+ - text/plain; charset=utf-8
+ 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
+ http_version: "1.1"
26 spec/fixtures/checkout/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PAYMENTREQUEST_0_AMT=9.00&AMT=9.00&RETURNURL=http%3a%2f%2fexample.com%2fthank_you&CANCELURL=http%3a%2f%2fexample.com%2fcanceled&PAYMENTREQUEST_0_CURRENCYCODE=USD&CURRENCYCODE=USD&DESC=Awesome%20-%20Monthly%20Subscription&PAYMENTREQUEST_0_DESC=Awesome%20-%20Monthly%20Subscription&L_BILLINGAGREEMENTDESCRIPTION0=Awesome%20-%20Monthly%20Subscription&PAYMENTREQUEST_0_NOTIFYURL=http%3a%2f%2fexample.com%2fpaypal%2fipn&NOTIFYURL=http%3a%2f%2fexample.com%2fpaypal%2fipn&PAYMENTREQUEST_0_PAYMENTACTION=Authorization&NOSHIPPING=1&L_BILLINGTYPE0=RecurringPayments&METHOD=SetExpressCheckout
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Sun, 26 Jun 2011 21:44:13 GMT
+ server:
+ - Apache
+ content-length:
+ - "136"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TOKEN=EC%2d7B902269MT603740W&TIMESTAMP=2011%2d06%2d26T21%3a44%3a14Z&CORRELATIONID=d7914d1fd83b9&ACK=Success&VERSION=72%2e0&BUILD=1936884
+ http_version: "1.1"
26 spec/fixtures/create_profile/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&METHOD=CreateRecurringPaymentsProfile
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:03:37 GMT
+ server:
+ - Apache
+ content-length:
+ - "933"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TIMESTAMP=2011%2d06%2d27T11%3a03%3a38Z&CORRELATIONID=cc5d656723747&ACK=Failure&VERSION=72%2e0&BUILD=1907759&L_ERRORCODE0=11585&L_ERRORCODE1=11518&L_ERRORCODE2=11516&L_ERRORCODE3=11519&L_ERRORCODE4=11549&L_SHORTMESSAGE0=Missing%20Token%20or%20payment%20source&L_SHORTMESSAGE1=Invalid%20billing%20period%2e&L_SHORTMESSAGE2=Invalid%20billing%20frequency&L_SHORTMESSAGE3=Invalid%20amount&L_SHORTMESSAGE4=Start%20Date%20is%20required&L_LONGMESSAGE0=Missing%20Token%20or%20buyer%20credit%20card&L_LONGMESSAGE1=Billing%20period%20must%20be%20one%20of%20Day%2c%20Week%2c%20SemiMonth%2c%20or%20Year&L_LONGMESSAGE2=Billing%20frequency%20must%20be%20%3e%200%20and%20be%20less%20than%20or%20equal%20to%20one%20year&L_LONGMESSAGE3=Bill%20amount%20must%20be%20greater%20than%200&L_LONGMESSAGE4=Subscription%20start%20date%20is%20required&L_SEVERITYCODE0=Error&L_SEVERITYCODE1=Error&L_SEVERITYCODE2=Error&L_SEVERITYCODE3=Error&L_SEVERITYCODE4=Error
+ http_version: "1.1"
26 spec/fixtures/create_profile/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PAYMENTREQUEST_0_AMT=9.00&AMT=9.00&PAYMENTREQUEST_0_CURRENCYCODE=USD&CURRENCYCODE=USD&DESC=Awesome%20-%20Monthly%20Subscription&PAYMENTREQUEST_0_DESC=Awesome%20-%20Monthly%20Subscription&L_BILLINGAGREEMENTDESCRIPTION0=Awesome%20-%20Monthly%20Subscription&PAYERID=WTTS5KC2T46YU&TOKEN=EC-2UK36172XH723314S&PROFILEREFERENCE=1234&PROFILESTARTDATE=2011-06-27T07%3a59%3a54Z&MAXFAILEDPAYMENTS=1&AUTOBILLOUTAMT=AddToNextBilling&PAYMENTREQUEST_0_NOTIFYURL=http%3a%2f%2fexample.com%2fpaypal%2fipn&NOTIFYURL=http%3a%2f%2fexample.com%2fpaypal%2fipn&BILLINGFREQUENCY=1&BILLINGPERIOD=Month&METHOD=CreateRecurringPaymentsProfile
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 10:59:58 GMT
+ server:
+ - Apache
+ content-length:
+ - "162"
+ content-type:
+ - text/plain; charset=utf-8
+ body: PROFILEID=I%2d89LD5VEHEVK4&PROFILESTATUS=ActiveProfile&TIMESTAMP=2011%2d06%2d27T11%3a00%3a01Z&CORRELATIONID=17ea90bec34e6&ACK=Success&VERSION=72%2e0&BUILD=1907759
+ http_version: "1.1"
26 spec/fixtures/details/cancelled.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&TOKEN=EC-365619347A138351P&METHOD=GetExpressCheckoutDetails
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 00:16:28 GMT
+ server:
+ - Apache
+ content-length:
+ - "1013"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TOKEN=EC%2d365619347A138351P&BILLINGAGREEMENTACCEPTEDSTATUS=0&CHECKOUTSTATUS=PaymentActionNotInitiated&TIMESTAMP=2011%2d06%2d27T00%3a16%3a30Z&CORRELATIONID=e25e70b5bf48a&ACK=Success&VERSION=72%2e0&BUILD=1936884&EMAIL=buyer_1308979708_per%40simplesideias%2ecom%2ebr&PAYERID=WTTS5KC2T46YU&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US&CURRENCYCODE=USD&AMT=9%2e00&SHIPPINGAMT=0%2e00&HANDLINGAMT=0%2e00&TAXAMT=0%2e00&DESC=Awesome%20%2d%20Monthly%20Subscription&NOTIFYURL=http%3a%2f%2fexample%2ecom%2fpaypal%2fipn&INSURANCEAMT=0%2e00&SHIPDISCAMT=0%2e00&PAYMENTREQUEST_0_CURRENCYCODE=USD&PAYMENTREQUEST_0_AMT=9%2e00&PAYMENTREQUEST_0_SHIPPINGAMT=0%2e00&PAYMENTREQUEST_0_HANDLINGAMT=0%2e00&PAYMENTREQUEST_0_TAXAMT=0%2e00&PAYMENTREQUEST_0_DESC=Awesome%20%2d%20Monthly%20Subscription&PAYMENTREQUEST_0_NOTIFYURL=http%3a%2f%2fexample%2ecom%2fpaypal%2fipn&PAYMENTREQUEST_0_INSURANCEAMT=0%2e00&PAYMENTREQUEST_0_SHIPDISCAMT=0%2e00&PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED=false&PAYMENTREQUESTINFO_0_ERRORCODE=0
+ http_version: "1.1"
26 spec/fixtures/details/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&METHOD=GetExpressCheckoutDetails
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 00:18:10 GMT
+ server:
+ - Apache
+ content-length:
+ - "214"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TIMESTAMP=2011%2d06%2d27T00%3a18%3a11Z&CORRELATIONID=c7975175bbfa4&ACK=Failure&VERSION=72%2e0&BUILD=1936884&L_ERRORCODE0=10410&L_SHORTMESSAGE0=Invalid%20token&L_LONGMESSAGE0=Invalid%20token%2e&L_SEVERITYCODE0=Error
+ http_version: "1.1"
26 spec/fixtures/details/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&TOKEN=EC-7B902269MT603740W&METHOD=GetExpressCheckoutDetails
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Sun, 26 Jun 2011 23:21:45 GMT
+ server:
+ - Apache
+ content-length:
+ - "1013"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TOKEN=EC%2d7B902269MT603740W&BILLINGAGREEMENTACCEPTEDSTATUS=1&CHECKOUTSTATUS=PaymentActionNotInitiated&TIMESTAMP=2011%2d06%2d26T23%3a21%3a45Z&CORRELATIONID=3013e52c8b3d2&ACK=Success&VERSION=72%2e0&BUILD=1936884&EMAIL=buyer_1308979708_per%40simplesideias%2ecom%2ebr&PAYERID=WTTS5KC2T46YU&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US&CURRENCYCODE=USD&AMT=9%2e00&SHIPPINGAMT=0%2e00&HANDLINGAMT=0%2e00&TAXAMT=0%2e00&DESC=Awesome%20%2d%20Monthly%20Subscription&NOTIFYURL=http%3a%2f%2fexample%2ecom%2fpaypal%2fipn&INSURANCEAMT=0%2e00&SHIPDISCAMT=0%2e00&PAYMENTREQUEST_0_CURRENCYCODE=USD&PAYMENTREQUEST_0_AMT=9%2e00&PAYMENTREQUEST_0_SHIPPINGAMT=0%2e00&PAYMENTREQUEST_0_HANDLINGAMT=0%2e00&PAYMENTREQUEST_0_TAXAMT=0%2e00&PAYMENTREQUEST_0_DESC=Awesome%20%2d%20Monthly%20Subscription&PAYMENTREQUEST_0_NOTIFYURL=http%3a%2f%2fexample%2ecom%2fpaypal%2fipn&PAYMENTREQUEST_0_INSURANCEAMT=0%2e00&PAYMENTREQUEST_0_SHIPDISCAMT=0%2e00&PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED=false&PAYMENTREQUESTINFO_0_ERRORCODE=0
+ http_version: "1.1"
26 spec/fixtures/payment/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PAYMENTREQUEST_0_AMT=9.00&AMT=9.00&PAYMENTREQUEST_0_CURRENCYCODE=USD&CURRENCYCODE=USD&DESC=Awesome%20-%20Monthly%20Subscription&PAYMENTREQUEST_0_DESC=Awesome%20-%20Monthly%20Subscription&L_BILLINGAGREEMENTDESCRIPTION0=Awesome%20-%20Monthly%20Subscription&PAYERID=WTTS5KC2T46YU&TOKEN=EC-7B902269MT603740W&PAYMENTREQUEST_0_PAYMENTACTION=Sale&METHOD=DoExpressCheckoutPayment
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 00:44:51 GMT
+ server:
+ - Apache
+ content-length:
+ - "338"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TIMESTAMP=2011%2d06%2d27T00%3a44%3a52Z&CORRELATIONID=2b242eadf3e74&ACK=Failure&VERSION=72%2e0&BUILD=1936884&L_ERRORCODE0=10411&L_SHORTMESSAGE0=This%20Express%20Checkout%20session%20has%20expired%2e&L_LONGMESSAGE0=This%20Express%20Checkout%20session%20has%20expired%2e%20%20Token%20value%20is%20no%20longer%20valid%2e&L_SEVERITYCODE0=Error
+ http_version: "1.1"
26 spec/fixtures/payment/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PAYMENTREQUEST_0_AMT=9.00&AMT=9.00&PAYMENTREQUEST_0_CURRENCYCODE=USD&CURRENCYCODE=USD&DESC=Awesome%20-%20Monthly%20Subscription&PAYMENTREQUEST_0_DESC=Awesome%20-%20Monthly%20Subscription&L_BILLINGAGREEMENTDESCRIPTION0=Awesome%20-%20Monthly%20Subscription&PAYERID=WTTS5KC2T46YU&TOKEN=EC-7A593227AC789800N&PAYMENTREQUEST_0_PAYMENTACTION=Sale&METHOD=DoExpressCheckoutPayment
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 00:51:19 GMT
+ server:
+ - Apache
+ content-length:
+ - "819"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TOKEN=EC%2d7A593227AC789800N&SUCCESSPAGEREDIRECTREQUESTED=false&TIMESTAMP=2011%2d06%2d27T00%3a51%3a24Z&CORRELATIONID=22c7539cdb7bd&ACK=Success&VERSION=72%2e0&BUILD=1936884&INSURANCEOPTIONSELECTED=false&SHIPPINGOPTIONISDEFAULT=false&PAYMENTINFO_0_TRANSACTIONID=5F811001LT4909607&PAYMENTINFO_0_TRANSACTIONTYPE=expresscheckout&PAYMENTINFO_0_PAYMENTTYPE=instant&PAYMENTINFO_0_ORDERTIME=2011%2d06%2d27T00%3a51%3a22Z&PAYMENTINFO_0_AMT=9%2e00&PAYMENTINFO_0_FEEAMT=0%2e56&PAYMENTINFO_0_TAXAMT=0%2e00&PAYMENTINFO_0_CURRENCYCODE=USD&PAYMENTINFO_0_PAYMENTSTATUS=Completed&PAYMENTINFO_0_PENDINGREASON=None&PAYMENTINFO_0_REASONCODE=None&PAYMENTINFO_0_PROTECTIONELIGIBILITY=Ineligible&PAYMENTINFO_0_PROTECTIONELIGIBILITYTYPE=None&PAYMENTINFO_0_SECUREMERCHANTACCOUNTID=DYJLBSW39VXJ8&PAYMENTINFO_0_ERRORCODE=0&PAYMENTINFO_0_ACK=Success
+ http_version: "1.1"
26 spec/fixtures/profile/cancel/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&ACTION=Cancel&PROFILEID=I-89LD5VEHEVK4&METHOD=ManageRecurringPaymentsProfileStatus
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:18:34 GMT
+ server:
+ - Apache
+ content-length:
+ - "387"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TIMESTAMP=2011%2d06%2d27T11%3a18%3a34Z&CORRELATIONID=d56246b888833&ACK=Failure&VERSION=72%2e0&BUILD=1907759&L_ERRORCODE0=11556&L_SHORTMESSAGE0=Invalid%20profile%20status%20for%20cancel%20action%3b%20profile%20should%20be%20active%20or%20suspended&L_LONGMESSAGE0=Invalid%20profile%20status%20for%20cancel%20action%3b%20profile%20should%20be%20active%20or%20suspended&L_SEVERITYCODE0=Error
+ http_version: "1.1"
26 spec/fixtures/profile/cancel/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&ACTION=Cancel&PROFILEID=I-89LD5VEHEVK4&METHOD=ManageRecurringPaymentsProfileStatus
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:17:58 GMT
+ server:
+ - Apache
+ content-length:
+ - "134"
+ content-type:
+ - text/plain; charset=utf-8
+ body: PROFILEID=I%2d89LD5VEHEVK4&TIMESTAMP=2011%2d06%2d27T11%3a18%3a00Z&CORRELATIONID=e148f6097d076&ACK=Success&VERSION=72%2e0&BUILD=1907759
+ http_version: "1.1"
26 spec/fixtures/profile/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PROFILEID=FOO&METHOD=GetRecurringPaymentsProfileDetails
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:22:27 GMT
+ server:
+ - Apache
+ content-length:
+ - "250"
+ content-type:
+ - text/plain; charset=utf-8
+ body: PROFILEID=FOO&TIMESTAMP=2011%2d06%2d27T11%3a22%3a27Z&CORRELATIONID=5ee1a2fc974cb&ACK=Failure&VERSION=72%2e0&BUILD=1907759&L_ERRORCODE0=11552&L_SHORTMESSAGE0=Invalid%20profile%20ID&L_LONGMESSAGE0=The%20profile%20ID%20is%20invalid&L_SEVERITYCODE0=Error
+ http_version: "1.1"
26 spec/fixtures/profile/reactivate/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&ACTION=Reactivate&PROFILEID=I-89LD5VEHEVK4&METHOD=ManageRecurringPaymentsProfileStatus
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:17:19 GMT
+ server:
+ - Apache
+ content-length:
+ - "367"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TIMESTAMP=2011%2d06%2d27T11%3a17%3a20Z&CORRELATIONID=3853b4ed4e0ef&ACK=Failure&VERSION=72%2e0&BUILD=1907759&L_ERRORCODE0=11558&L_SHORTMESSAGE0=Invalid%20profile%20status%20for%20reactivate%20action%3b%20profile%20should%20be%20suspended&L_LONGMESSAGE0=Invalid%20profile%20status%20for%20reactivate%20action%3b%20profile%20should%20be%20suspended&L_SEVERITYCODE0=Error
+ http_version: "1.1"
26 spec/fixtures/profile/reactivate/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&ACTION=Reactivate&PROFILEID=I-89LD5VEHEVK4&METHOD=ManageRecurringPaymentsProfileStatus
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:16:55 GMT
+ server:
+ - Apache
+ content-length:
+ - "134"
+ content-type:
+ - text/plain; charset=utf-8
+ body: PROFILEID=I%2d89LD5VEHEVK4&TIMESTAMP=2011%2d06%2d27T11%3a16%3a57Z&CORRELATIONID=d7fdc3e9ce240&ACK=Success&VERSION=72%2e0&BUILD=1907759
+ http_version: "1.1"
26 spec/fixtures/profile/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&PROFILEID=I-89LD5VEHEVK4&METHOD=GetRecurringPaymentsProfileDetails
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:20:43 GMT
+ server:
+ - Apache
+ content-length:
+ - "947"
+ content-type:
+ - text/plain; charset=utf-8
+ body: PROFILEID=I%2d89LD5VEHEVK4&STATUS=Cancelled&AUTOBILLOUTAMT=AddToNextBilling&DESC=Awesome%20%2d%20Monthly%20Subscription&MAXFAILEDPAYMENTS=1&SUBSCRIBERNAME=Test%20User&PROFILESTARTDATE=2011%2d06%2d27T07%3a00%3a00Z&PROFILEREFERENCE=1234&NUMCYCLESCOMPLETED=1&NUMCYCLESREMAINING=18446744073709551615&OUTSTANDINGBALANCE=0%2e00&FAILEDPAYMENTCOUNT=0&LASTPAYMENTDATE=2011%2d06%2d27T11%3a00%3a11Z&LASTPAYMENTAMT=9%2e00&TRIALAMTPAID=0%2e00&REGULARAMTPAID=9%2e00&AGGREGATEAMT=9%2e00&AGGREGATEOPTIONALAMT=0%2e00&FINALPAYMENTDUEDATE=1970%2d01%2d01T00%3a00%3a00Z&TIMESTAMP=2011%2d06%2d27T11%3a20%3a44Z&CORRELATIONID=bd01cda849f4d&ACK=Success&VERSION=72%2e0&BUILD=1907759&BILLINGPERIOD=Month&BILLINGFREQUENCY=1&TOTALBILLINGCYCLES=0&CURRENCYCODE=USD&AMT=9%2e00&SHIPPINGAMT=0%2e00&TAXAMT=0%2e00&REGULARBILLINGPERIOD=Month&REGULARBILLINGFREQUENCY=1&REGULARTOTALBILLINGCYCLES=0&REGULARCURRENCYCODE=USD&REGULARAMT=9%2e00&REGULARSHIPPINGAMT=0%2e00&REGULARTAXAMT=0%2e00
+ http_version: "1.1"
26 spec/fixtures/profile/suspend/failure.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&ACTION=Suspend&PROFILEID=I-89LD5VEHEVK4&METHOD=ManageRecurringPaymentsProfileStatus
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:13:19 GMT
+ server:
+ - Apache
+ content-length:
+ - "355"
+ content-type:
+ - text/plain; charset=utf-8
+ body: TIMESTAMP=2011%2d06%2d27T11%3a13%3a20Z&CORRELATIONID=1c35a16760463&ACK=Failure&VERSION=72%2e0&BUILD=1907759&L_ERRORCODE0=11557&L_SHORTMESSAGE0=Invalid%20profile%20status%20for%20suspend%20action%3b%20profile%20should%20be%20active&L_LONGMESSAGE0=Invalid%20profile%20status%20for%20suspend%20action%3b%20profile%20should%20be%20active&L_SEVERITYCODE0=Error
+ http_version: "1.1"
26 spec/fixtures/profile/suspend/success.yml
@@ -0,0 +1,26 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://api-3t.sandbox.paypal.com:443/nvp
+ body: USER=seller_1308793919_biz_api1.simplesideias.com.br&PWD=1308793931&SIGNATURE=AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y&VERSION=72.0&ACTION=Suspend&PROFILEID=I-89LD5VEHEVK4&METHOD=ManageRecurringPaymentsProfileStatus
+ headers:
+ user-agent:
+ - PayPal::Recurring/0.1.0
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ date:
+ - Mon, 27 Jun 2011 11:12:24 GMT
+ server:
+ - Apache
+ content-length:
+ - "134"
+ content-type:
+ - text/plain; charset=utf-8
+ body: PROFILEID=I%2d89LD5VEHEVK4&TIMESTAMP=2011%2d06%2d27T11%3a12%3a26Z&CORRELATIONID=a2f9cf29d9af6&ACK=Success&VERSION=72%2e0&BUILD=1907759
+ http_version: "1.1"
87 spec/paypal/recurring_spec.rb
@@ -0,0 +1,87 @@
+require "spec_helper"
+
+describe PayPal::Recurring do
+ describe ".logger" do
+ it "has a default logging" do
+ PayPal::Recurring.logger.should be_a(Logger)
+ end
+ end
+
+ describe ".new" do
+ it "instantiates PayPal::Recurring::Base" do
+ PayPal::Recurring.new.should be_a(PayPal::Recurring::Base)
+ end
+ end
+
+ describe ".version" do
+ it "returns PayPal's API version" do
+ PayPal::Recurring.api_version.should eq("72.0")
+ end
+ end
+
+ describe ".configure" do
+ it "yields PayPal::Recurring" do
+ PayPal::Recurring.configure do |config|
+ config.should be(PayPal::Recurring)
+ end
+ end
+
+ it "sets attributes" do
+ PayPal::Recurring.configure do |config|
+ config.sandbox = false
+ end
+
+ PayPal::Recurring.sandbox.should be_false
+ end
+ end
+
+ describe ".sandbox?" do
+ it "detects sandbox" do
+ PayPal::Recurring.sandbox = true
+ PayPal::Recurring.should be_sandbox
+ end
+
+ it "ignores sandbox" do
+ PayPal::Recurring.sandbox = false
+ PayPal::Recurring.should_not be_sandbox
+ end
+ end
+
+ describe ".environment" do
+ it "returns production" do
+ PayPal::Recurring.sandbox = false
+ PayPal::Recurring.environment.should eq(:production)
+ end
+
+ it "returns sandbox" do
+ PayPal::Recurring.sandbox = true
+ PayPal::Recurring.environment.should eq(:sandbox)
+ end
+ end
+
+ describe ".api_endpoint" do
+ it "returns url" do
+ PayPal::Recurring.api_endpoint.should_not be_nil
+ PayPal::Recurring.api_endpoint.should == PayPal::Recurring.endpoints[:api]
+ end
+ end
+
+ describe ".site_endpoint" do
+ it "returns url" do
+ PayPal::Recurring.site_endpoint.should_not be_nil
+ PayPal::Recurring.site_endpoint.should == PayPal::Recurring.endpoints[:site]
+ end
+ end
+
+ describe ".endpoints" do
+ it "returns production's" do
+ PayPal::Recurring.sandbox = false
+ PayPal::Recurring.endpoints.should eq(PayPal::Recurring::ENDPOINTS[:production])
+ end
+
+ it "returns sandbox's" do
+ PayPal::Recurring.sandbox = true
+ PayPal::Recurring.endpoints.should eq(PayPal::Recurring::ENDPOINTS[:sandbox])
+ end
+ end
+end
102 spec/paypal/request_spec.rb
@@ -0,0 +1,102 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Request do
+ describe "#client" do
+ it "uses SSL" do
+ subject.client.use_ssl?.should be_true
+ end
+
+ it "verifies certificate" do
+ subject.client.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
+ end
+
+ it "sets CA file to bundled file" do
+ subject.client.ca_file.should == File.expand_path("../../../lib/paypal/recurring/cacert.pem", __FILE__)
+ File.should be_file(subject.client.ca_file)
+ end
+ end
+
+ describe "#post" do
+ let(:request) {
+ FakeWeb.register_uri :post, "https://api-3t.sandbox.paypal.com/nvp", :status => 200
+ subject.post(:checkout)
+ CGI.parse(FakeWeb.last_request.body)
+ }
+
+ it "sets action" do
+ request["METHOD"].first.should == "SetExpressCheckout"
+ end
+
+ it "sets username" do
+ request["USER"].first.should == PayPal::Recurring.username
+ end
+
+ it "sets password" do
+ request["PWD"].first.should == PayPal::Recurring.password
+ end
+
+ it "sets signature" do
+ request["SIGNATURE"].first.should == PayPal::Recurring.signature
+ end
+
+ it "sets API version" do
+ request["VERSION"].first.should == PayPal::Recurring.api_version
+ end
+
+ it "sets user agent" do
+ FakeWeb.last_request["User-Agent"].should == "PayPal::Recurring/#{PayPal::Recurring::Version::STRING}"
+ end
+ end
+
+ describe "#normalize_params" do
+ it "normalizes method" do
+ subject.normalize_params(:method => "some method").should == {:METHOD => "some method"}
+ end
+
+ it "normalizes return_url" do
+ subject.normalize_params(:return_url => "http://example.com").should == {:RETURNURL => "http://example.com"}
+ end
+
+ it "normalizes cancel_url" do
+ subject.normalize_params(:cancel_url => "http://example.com").should == {:CANCELURL => "http://example.com"}
+ end
+
+ it "normalizes amount" do
+ subject.normalize_params(:amount => "9.00").should == {:PAYMENTREQUEST_0_AMT => "9.00", :AMT => "9.00"}
+ end
+
+ it "normalizes currency" do
+ subject.normalize_params(:currency => "USD").should == {:CURRENCYCODE => "USD", :PAYMENTREQUEST_0_CURRENCYCODE => "USD"}
+ end
+
+ it "normalizes description" do
+ subject.normalize_params(:description => "Awesome").should == {:DESC => "Awesome", :PAYMENTREQUEST_0_DESC => "Awesome", :L_BILLINGAGREEMENTDESCRIPTION0 => "Awesome"}
+ end
+
+ it "normalizes ipn url" do
+ subject.normalize_params(:ipn_url => "http://example.com").should == {:PAYMENTREQUEST_0_NOTIFYURL => "http://example.com", :NOTIFYURL => "http://example.com"}
+ end
+
+ it "normalizes period" do
+ subject.normalize_params(:period => :monthly).should == {:BILLINGPERIOD => "Month"}
+ subject.normalize_params(:period => :daily).should == {:BILLINGPERIOD => "Day"}
+ subject.normalize_params(:period => :yearly).should == {:BILLINGPERIOD => "Year"}
+ end
+
+ it "normalizes start at" do
+ date = Time.parse("2011-06-26 15:13:00")
+ subject.normalize_params(:start_at => date).should == {:PROFILESTARTDATE => "2011-06-26T15:13:00Z"}
+ end
+
+ it "normalizes outstanding" do
+ subject.normalize_params(:outstanding => :next_billing).should == {:AUTOBILLOUTAMT => "AddToNextBilling"}
+ subject.normalize_params(:outstanding => :no_auto).should == {:AUTOBILLOUTAMT => "NoAutoBill"}
+ end
+
+ it "normalizes action" do
+ subject.normalize_params(:action => :cancel).should == {:ACTION => "Cancel"}
+ subject.normalize_params(:action => :suspend).should == {:ACTION => "Suspend"}
+ subject.normalize_params(:action => :reactivate).should == {:ACTION => "Reactivate"}
+ end
+ end
+end
51 spec/paypal/response/checkout_details_spec.rb
@@ -0,0 +1,51 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Response::Details do
+ context "when successful" do
+ use_vcr_cassette "details/success"
+
+ subject {
+ ppr = PayPal::Recurring.new(:token => "EC-7B902269MT603740W")
+ ppr.checkout_details
+ }
+
+ it { should be_valid }
+ it { should be_success }
+
+ its(:errors) { should be_empty }
+ its(:status) { should == "PaymentActionNotInitiated" }
+ its(:email) { should == "buyer_1308979708_per@simplesideias.com.br" }
+ its(:requested_at) { should be_a(Time) }
+ its(:email) { should == "buyer_1308979708_per@simplesideias.com.br" }
+ its(:payer_id) { should == "WTTS5KC2T46YU" }
+ its(:payer_status) { should == "verified" }
+ its(:country) { should == "US" }
+ its(:currency) { should == "USD" }
+ its(:description) { should == "Awesome - Monthly Subscription" }
+ its(:ipn_url) { should == "http://example.com/paypal/ipn" }
+ its(:agreed?) { should be_true }
+ end
+
+ context "when cancelled" do
+ use_vcr_cassette "details/cancelled"
+ subject {
+ ppr = PayPal::Recurring.new(:token => "EC-365619347A138351P")
+ ppr.checkout_details
+ }
+
+ it { should be_valid }
+ it { should be_success }
+
+ its(:agreed?) { should be_false }
+ end
+
+ context "when failure" do
+ use_vcr_cassette("details/failure")
+ subject { PayPal::Recurring.new.checkout_details }
+
+ it { should_not be_valid }
+ it { should_not be_success }
+
+ its(:errors) { should have(1).item }
+ end
+end
32 spec/paypal/response/checkout_spec.rb
@@ -0,0 +1,32 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Response::Checkout do
+ context "when successful" do
+ use_vcr_cassette "checkout/success"
+
+ subject {
+ ppr = PayPal::Recurring.new({
+ :return_url => "http://example.com/thank_you",
+ :cancel_url => "http://example.com/canceled",
+ :ipn_url => "http://example.com/paypal/ipn",
+ :description => "Awesome - Monthly Subscription",
+ :amount => "9.00",
+ :currency => "USD"
+ })
+
+ ppr.checkout
+ }
+
+ its(:valid?) { should be_true }
+ its(:errors) { should be_empty }
+ its(:checkout_url) { should == "#{PayPal::Recurring.site_endpoint}?cmd=_express-checkout&token=EC-7B902269MT603740W" }
+ end
+
+ context "when failure" do
+ use_vcr_cassette("checkout/failure")
+ subject { PayPal::Recurring.new.checkout }
+
+ its(:valid?) { should be_false }
+ its(:errors) { should have(3).items }
+ end
+end
39 spec/paypal/response/create_recurring_profile_spec.rb
@@ -0,0 +1,39 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Response::Profile do
+ context "when successful" do
+ use_vcr_cassette "create_profile/success"
+
+ subject {
+ ppr = PayPal::Recurring.new({
+ :amount => "9.00",
+ :currency => "USD",
+ :description => "Awesome - Monthly Subscription",
+ :ipn_url => "http://example.com/paypal/ipn",
+ :frequency => 1,
+ :token => "EC-2UK36172XH723314S",
+ :period => :monthly,
+ :reference => "1234",
+ :payer_id => "WTTS5KC2T46YU",
+ :start_at => Time.now,
+ :failed => 1,
+ :outstanding => :next_billing
+ })
+ ppr.create_recurring_profile
+ }
+
+ it { should be_valid }
+
+ its(:profile_id) { should == "I-89LD5VEHEVK4" }
+ its(:status) { should == "ActiveProfile" }
+ its(:errors) { should be_empty }
+ end
+
+ context "when failure" do
+ use_vcr_cassette("create_profile/failure")
+ subject { PayPal::Recurring.new.create_recurring_profile }
+
+ it { should_not be_valid }
+ its(:errors) { should have(5).items }
+ end
+end
62 spec/paypal/response/manage_profile_spec.rb
@@ -0,0 +1,62 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Response::ManageProfile do
+ let(:paypal) { PayPal::Recurring.new(:profile_id => "I-89LD5VEHEVK4") }
+
+ context "canceling" do
+ context "when successful" do
+ use_vcr_cassette "profile/cancel/success"
+ subject { paypal.cancel }
+
+ it { should be_success }
+ it { should be_valid }
+ end
+
+ context "when failure" do
+ use_vcr_cassette "profile/cancel/failure"
+ subject { paypal.cancel }
+
+ it { should_not be_success }
+ it { should_not be_valid }
+ its(:errors) { should have(1).item }
+ end
+ end
+
+ context "reactivating" do
+ context "when successful" do
+ use_vcr_cassette "profile/reactivate/success"
+ subject { paypal.reactivate }
+
+ it { should be_success }
+ it { should be_valid }
+ end
+
+ context "when failure" do
+ use_vcr_cassette "profile/reactivate/failure"
+ subject { paypal.reactivate }
+
+ it { should_not be_success }
+ it { should_not be_valid }
+ its(:errors) { should have(1).item }
+ end
+ end
+
+ context "suspending" do
+ context "when successful" do
+ use_vcr_cassette "profile/suspend/success"
+ subject { paypal.suspend }
+
+ it { should be_success }
+ it { should be_valid }
+ end
+
+ context "when failure" do
+ use_vcr_cassette "profile/suspend/failure"
+ subject { paypal.suspend }
+
+ it { should_not be_success }
+ it { should_not be_valid }
+ its(:errors) { should have(1).item }
+ end
+ end
+end
41 spec/paypal/response/profile_spec.rb
@@ -0,0 +1,41 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Response::Profile do
+ let(:paypal) { PayPal::Recurring.new(:profile_id => "I-89LD5VEHEVK4") }
+
+ context "when successful" do
+ use_vcr_cassette "profile/success"
+ subject { paypal.profile }
+
+ it { should_not be_active }
+
+ its(:status) { should == :canceled }
+ its(:profile_id) { should == "I-89LD5VEHEVK4" }
+ its(:outstanding) { should == :next_billing }
+ its(:description) { should == "Awesome - Monthly Subscription" }
+ its(:payer_name) { should == "Test User" }
+ its(:reference) { should == "1234" }
+ its(:failed) { should == "1" }
+ its(:start_at) { should be_a(Time) }
+ its(:completed) { should == "1" }
+ its(:remaining) { should == "18446744073709551615" }
+ its(:outstanding_balance) { should == "0.00" }
+ its(:failed_count) { should == "0" }
+ its(:last_payment_date) { should be_a(Time) }
+ its(:last_payment_amount) { should == "9.00" }
+ its(:period) { should == :monthly }
+ its(:frequency) { should == "1" }
+ its(:currency) { should == "USD" }
+ its(:amount) { should == "9.00" }
+ end
+
+ context "when failure" do
+ use_vcr_cassette "profile/failure"
+ subject { paypal.profile }
+
+ it { should_not be_valid }
+ it { should_not be_success }
+
+ its(:errors) { should have(1).item }
+ end
+end
35 spec/paypal/response/request_payment_spec.rb
@@ -0,0 +1,35 @@
+require "spec_helper"
+
+describe PayPal::Recurring::Response::Payment do
+ context "when successful" do
+ use_vcr_cassette "payment/success"
+
+ subject {
+ ppr = PayPal::Recurring.new({
+ :description => "Awesome - Monthly Subscription",
+ :amount => "9.00",
+ :currency => "USD",
+ :payer_id => "WTTS5KC2T46YU",
+ :token => "EC-7A593227AC789800N",
+ })
+ ppr.request_payment
+ }
+
+ it { should be_valid }
+ it { should be_completed }
+ it { should be_approved }
+
+ its(:errors) { should be_empty }
+ end
+
+ context "when failure" do
+ use_vcr_cassette("payment/failure")
+ subject { PayPal::Recurring.new.request_payment }
+
+ it { should_not be_valid }
+ it { should_not be_completed }
+ it { should_not be_approved }
+
+ its(:errors) { should have(1).items }
+ end
+end
24 spec/spec_helper.rb
@@ -0,0 +1,24 @@
+require "bundler"
+Bundler.setup(:default, :development)
+Bundler.require
+
+require "paypal-recurring"
+require "vcr"
+
+VCR.config do |config|
+ config.cassette_library_dir = File.dirname(__FILE__) + "/fixtures"
+ config.stub_with :fakeweb
+end
+
+RSpec.configure do |config|
+ config.extend VCR::RSpec::Macros
+
+ config.before do
+ PayPal::Recurring.configure do |config|
+ config.sandbox = true
+ config.username = "seller_1308793919_biz_api1.simplesideias.com.br"
+ config.password = "1308793931"
+ config.signature = "AFcWxV21C7fd0v3bYYYRCpSSRl31AzaB6TzXx5amObyEghjU13.0av2Y"
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.