Repository: nov/paypal-express Branch: master Commit: 198de9274df8 Files: 85 Total size: 119.0 KB Directory structure: gitextract_wg6sknv5/ ├── .document ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.rdoc ├── Rakefile ├── VERSION ├── lib/ │ ├── paypal/ │ │ ├── base.rb │ │ ├── exception/ │ │ │ ├── api_error.rb │ │ │ └── http_error.rb │ │ ├── exception.rb │ │ ├── express/ │ │ │ ├── request.rb │ │ │ └── response.rb │ │ ├── express.rb │ │ ├── ipn.rb │ │ ├── nvp/ │ │ │ ├── request.rb │ │ │ └── response.rb │ │ ├── payment/ │ │ │ ├── common/ │ │ │ │ └── amount.rb │ │ │ ├── recurring/ │ │ │ │ ├── activation.rb │ │ │ │ ├── billing.rb │ │ │ │ └── summary.rb │ │ │ ├── recurring.rb │ │ │ ├── request/ │ │ │ │ └── item.rb │ │ │ ├── request.rb │ │ │ ├── response/ │ │ │ │ ├── address.rb │ │ │ │ ├── info.rb │ │ │ │ ├── item.rb │ │ │ │ ├── payer.rb │ │ │ │ ├── reference.rb │ │ │ │ └── refund.rb │ │ │ └── response.rb │ │ └── util.rb │ └── paypal.rb ├── paypal-express.gemspec └── spec/ ├── fake_response/ │ ├── BillAgreementUpdate/ │ │ ├── fetch.txt │ │ └── revoke.txt │ ├── CreateBillingAgreement/ │ │ └── success.txt │ ├── CreateRecurringPaymentsProfile/ │ │ ├── failure.txt │ │ └── success.txt │ ├── DoCapture/ │ │ ├── failure.txt │ │ └── success.txt │ ├── DoExpressCheckoutPayment/ │ │ ├── failure.txt │ │ ├── success.txt │ │ ├── success_with_billing_agreement.txt │ │ └── success_with_many_items.txt │ ├── DoReferenceTransaction/ │ │ ├── failure.txt │ │ └── success.txt │ ├── DoVoid/ │ │ └── success.txt │ ├── GetExpressCheckoutDetails/ │ │ ├── failure.txt │ │ ├── success.txt │ │ └── with_billing_accepted_status.txt │ ├── GetRecurringPaymentsProfileDetails/ │ │ ├── failure.txt │ │ └── success.txt │ ├── GetTransactionDetails/ │ │ ├── failure.txt │ │ └── success.txt │ ├── IPN/ │ │ ├── invalid.txt │ │ └── valid.txt │ ├── ManageRecurringPaymentsProfileStatus/ │ │ ├── failure.txt │ │ └── success.txt │ ├── RefundTransaction/ │ │ └── full.txt │ └── SetExpressCheckout/ │ ├── failure.txt │ └── success.txt ├── helpers/ │ └── fake_response_helper.rb ├── paypal/ │ ├── exception/ │ │ ├── api_error_spec.rb │ │ └── http_error_spec.rb │ ├── express/ │ │ ├── request_spec.rb │ │ └── response_spec.rb │ ├── ipn_spec.rb │ ├── nvp/ │ │ ├── request_spec.rb │ │ └── response_spec.rb │ ├── payment/ │ │ ├── common/ │ │ │ └── amount_spec.rb │ │ ├── recurring/ │ │ │ └── activation_spec.rb │ │ ├── recurring_spec.rb │ │ ├── request/ │ │ │ └── item_spec.rb │ │ ├── request_spec.rb │ │ ├── response/ │ │ │ ├── address_spec.rb │ │ │ ├── info_spec.rb │ │ │ ├── item_spec.rb │ │ │ └── payer_spec.rb │ │ └── response_spec.rb │ └── util_spec.rb └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .document ================================================ README.rdoc lib/**/*.rb bin/* features/**/*.feature LICENSE ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: nov ================================================ FILE: .gitignore ================================================ ## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg ## PROJECT::SPECIFIC Gemfile.lock ================================================ FILE: .rspec ================================================ --color --format=documentation ================================================ FILE: .travis.yml ================================================ before_install: - gem install bundler rvm: - 2.0.0 - 2.1.2 ================================================ FILE: Gemfile ================================================ source 'http://rubygems.org' platform :jruby do gem 'jruby-openssl', '>= 0.7' end gemspec ================================================ FILE: LICENSE ================================================ Copyright (c) 2011 nov matake 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. ================================================ FILE: README.rdoc ================================================ = paypal-express Handle PayPal Express Checkout. Both Instance Payment and Recurring Payment are supported. Express Checkout for Digital Goods is also supported. {}[http://travis-ci.org/nov/paypal-express] == Installation gem install paypal-express == Usage See Wiki on Github https://github.com/nov/paypal-express/wiki Play with Sample Rails App https://github.com/nov/paypal-express-sample https://paypal-express-sample.heroku.com == Note on Patches/Pull Requests * Fork the project. * Make your feature addition or bug fix. * Add tests for it. This is important so I don't break it in a future version unintentionally. * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) * Send me a pull request. Bonus points for topic branches. == Copyright Copyright (c) 2011 nov matake. See LICENSE for details. ================================================ FILE: Rakefile ================================================ require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) namespace :coverage do desc "Open coverage report" task :report do require 'simplecov' `open "#{File.join SimpleCov.coverage_path, 'index.html'}"` end end task :spec do Rake::Task[:'coverage:report'].invoke unless ENV['TRAVIS_RUBY_VERSION'] end task :default => :spec ================================================ FILE: VERSION ================================================ 0.8.1 ================================================ FILE: lib/paypal/base.rb ================================================ module Paypal class Base include AttrRequired, AttrOptional, Util def initialize(attributes = {}) if attributes.is_a?(Hash) (required_attributes + optional_attributes).each do |key| value = if numeric_attribute?(key) Util.to_numeric(attributes[key]) else attributes[key] end self.send "#{key}=", value end end attr_missing! end end end ================================================ FILE: lib/paypal/exception/api_error.rb ================================================ module Paypal class Exception class APIError < Exception attr_accessor :response def initialize(response = {}) @response = if response.is_a?(Hash) Response.new response else response end end def message if response.respond_to?(:short_messages) && response.short_messages.any? "PayPal API Error: " << response.short_messages.map{ |m| "'#{m}'" }.join(", ") else "PayPal API Error" end end class Response cattr_reader :attribute_mapping @@attribute_mapping = { :ACK => :ack, :BUILD => :build, :CORRELATIONID => :correlation_id, :TIMESTAMP => :timestamp, :VERSION => :version, :ORDERTIME => :order_time, :PENDINGREASON => :pending_reason, :PAYMENTSTATUS => :payment_status, :PAYMENTTYPE => :payment_type, :REASONCODE => :reason_code, :TRANSACTIONTYPE => :transaction_type } attr_accessor *@@attribute_mapping.values attr_accessor :raw, :details alias_method :colleration_id, :correlation_id # NOTE: I made a typo :p class Detail cattr_reader :attribute_mapping @@attribute_mapping = { :ERRORCODE => :error_code, :SEVERITYCODE => :severity_code, :LONGMESSAGE => :long_message, :SHORTMESSAGE => :short_message } attr_accessor *@@attribute_mapping.values def initialize(attributes = {}) @raw = attributes attrs = attributes.dup @@attribute_mapping.each do |key, value| self.send "#{value}=", attrs.delete(key) end # warn ignored params attrs.each do |key, value| Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn end end end def initialize(attributes = {}) attrs = attributes.dup @raw = attributes @@attribute_mapping.each do |key, value| self.send "#{value}=", attrs.delete(key) end details = [] attrs.keys.each do |attribute| key, index = attribute.to_s.scan(/^L_(\S+)(\d+)$/).first next if [key, index].any?(&:blank?) details[index.to_i] ||= {} details[index.to_i][key.to_sym] = attrs.delete(attribute) end @details = details.collect do |_attrs_| Detail.new _attrs_ end # warn ignored params attrs.each do |key, value| Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn end end def short_messages details.map(&:short_message).compact end end end end end ================================================ FILE: lib/paypal/exception/http_error.rb ================================================ module Paypal class Exception class HttpError < Exception attr_accessor :code, :message, :body def initialize(code, message, body = '') @code = code @message = message @body = body end end end end ================================================ FILE: lib/paypal/exception.rb ================================================ module Paypal class Exception < StandardError end end ================================================ FILE: lib/paypal/express/request.rb ================================================ module Paypal module Express class Request < NVP::Request # Common def setup(payment_requests, return_url, cancel_url, options = {}) params = { :RETURNURL => return_url, :CANCELURL => cancel_url } if options[:no_shipping] params[:REQCONFIRMSHIPPING] = 0 params[:NOSHIPPING] = 1 end params[:ALLOWNOTE] = 0 if options[:allow_note] == false { :solution_type => :SOLUTIONTYPE, :landing_page => :LANDINGPAGE, :email => :EMAIL, :brand => :BRANDNAME, :locale => :LOCALECODE, :logo => :LOGOIMG, :cart_border_color => :CARTBORDERCOLOR, :payflow_color => :PAYFLOWCOLOR }.each do |option_key, param_key| params[param_key] = options[option_key] if options[option_key] end Array(payment_requests).each_with_index do |payment_request, index| params.merge! payment_request.to_params(index) end response = self.request :SetExpressCheckout, params Response.new response, options end def details(token) response = self.request :GetExpressCheckoutDetails, {:TOKEN => token} Response.new response end def transaction_details(transaction_id) response = self.request :GetTransactionDetails, {:TRANSACTIONID=> transaction_id} Response.new response end def checkout!(token, payer_id, payment_requests) params = { :TOKEN => token, :PAYERID => payer_id } Array(payment_requests).each_with_index do |payment_request, index| params.merge! payment_request.to_params(index) end response = self.request :DoExpressCheckoutPayment, params Response.new response end def capture!(authorization_id, amount, currency_code, complete_type = 'Complete') params = { :AUTHORIZATIONID => authorization_id, :COMPLETETYPE => complete_type, :AMT => amount, :CURRENCYCODE => currency_code } response = self.request :DoCapture, params Response.new response end def void!(authorization_id, params={}) params = { :AUTHORIZATIONID => authorization_id, :NOTE => params[:note] } response = self.request :DoVoid, params Response.new response end # Recurring Payment Specific def subscribe!(token, recurring_profile) params = { :TOKEN => token } params.merge! recurring_profile.to_params response = self.request :CreateRecurringPaymentsProfile, params Response.new response end def subscription(profile_id) params = { :PROFILEID => profile_id } response = self.request :GetRecurringPaymentsProfileDetails, params Response.new response end def renew!(profile_id, action, options = {}) params = { :PROFILEID => profile_id, :ACTION => action } if options[:note] params[:NOTE] = options[:note] end response = self.request :ManageRecurringPaymentsProfileStatus, params Response.new response end def suspend!(profile_id, options = {}) renew!(profile_id, :Suspend, options) end def cancel!(profile_id, options = {}) renew!(profile_id, :Cancel, options) end def reactivate!(profile_id, options = {}) renew!(profile_id, :Reactivate, options) end # Reference Transaction Specific def agree!(token, options = {}) params = { :TOKEN => token } if options[:max_amount] params[:MAXAMT] = Util.formatted_amount options[:max_amount] end response = self.request :CreateBillingAgreement, params Response.new response end def agreement(reference_id) params = { :REFERENCEID => reference_id } response = self.request :BillAgreementUpdate, params Response.new response end def charge!(reference_id, amount, options = {}) params = { :REFERENCEID => reference_id, :AMT => Util.formatted_amount(amount), :PAYMENTACTION => options[:payment_action] || :Sale } if options[:currency_code] params[:CURRENCYCODE] = options[:currency_code] end response = self.request :DoReferenceTransaction, params Response.new response end def revoke!(reference_id) params = { :REFERENCEID => reference_id, :BillingAgreementStatus => :Canceled } response = self.request :BillAgreementUpdate, params Response.new response end # Refund Specific def refund!(transaction_id, options = {}) params = { :TRANSACTIONID => transaction_id, :REFUNDTYPE => :Full } if options[:invoice_id] params[:INVOICEID] = options[:invoice_id] end if options[:type] params[:REFUNDTYPE] = options[:type] params[:AMT] = options[:amount] params[:CURRENCYCODE] = options[:currency_code] end if options[:note] params[:NOTE] = options[:note] end response = self.request :RefundTransaction, params Response.new response end end end end ================================================ FILE: lib/paypal/express/response.rb ================================================ module Paypal module Express class Response < NVP::Response attr_accessor :pay_on_paypal, :mobile def initialize(response, options = {}) super response @pay_on_paypal = options[:pay_on_paypal] @mobile = options[:mobile] end def redirect_uri endpoint = URI.parse Paypal.endpoint endpoint.query = query(:with_cmd).to_query endpoint.to_s end def popup_uri endpoint = URI.parse Paypal.popup_endpoint endpoint.query = query.to_query endpoint.to_s end private def query(with_cmd = false) _query_ = {:token => self.token} _query_.merge!(:cmd => '_express-checkout') if with_cmd _query_.merge!(:cmd => '_express-checkout-mobile') if mobile _query_.merge!(:useraction => 'commit') if pay_on_paypal _query_ end end end end ================================================ FILE: lib/paypal/express.rb ================================================ require 'paypal' ================================================ FILE: lib/paypal/ipn.rb ================================================ module Paypal module IPN def self.endpoint _endpoint_ = URI.parse Paypal.endpoint _endpoint_.query = { :cmd => '_notify-validate' }.to_query _endpoint_.to_s end def self.verify!(raw_post) response = RestClient.post( endpoint, raw_post ) case response.body when 'VERIFIED' true else raise Exception::APIError.new(response.body) end end end end ================================================ FILE: lib/paypal/nvp/request.rb ================================================ module Paypal module NVP class Request < Base attr_required :username, :password, :signature attr_optional :subject attr_accessor :version ENDPOINT = { :production => 'https://api-3t.paypal.com/nvp', :sandbox => 'https://api-3t.sandbox.paypal.com/nvp' } def self.endpoint if Paypal.sandbox? ENDPOINT[:sandbox] else ENDPOINT[:production] end end def initialize(attributes = {}) @version = Paypal.api_version super end def common_params { :USER => self.username, :PWD => self.password, :SIGNATURE => self.signature, :SUBJECT => self.subject, :VERSION => self.version } end def request(method, params = {}) handle_response do post(method, params) end end private def post(method, params) RestClient.post(self.class.endpoint, common_params.merge(params).merge(:METHOD => method)) end def handle_response response = yield response = CGI.parse(response).inject({}) do |res, (k, v)| res.merge!(k.to_sym => v.first) end case response[:ACK] when 'Success', 'SuccessWithWarning' response else raise Exception::APIError.new(response) end rescue RestClient::Exception => e raise Exception::HttpError.new(e.http_code, e.message, e.http_body) end end end end ================================================ FILE: lib/paypal/nvp/response.rb ================================================ module Paypal module NVP class Response < Base cattr_reader :attribute_mapping @@attribute_mapping = { :ACK => :ack, :BUILD => :build, :BILLINGAGREEMENTACCEPTEDSTATUS => :billing_agreement_accepted_status, :CHECKOUTSTATUS => :checkout_status, :CORRELATIONID => :correlation_id, :COUNTRYCODE => :country_code, :CURRENCYCODE => :currency_code, :DESC => :description, :NOTIFYURL => :notify_url, :TIMESTAMP => :timestamp, :TOKEN => :token, :VERSION => :version, # Some of the attributes below are duplicates of what # exists in the payment response, but paypal doesn't # prefix these with PAYMENTREQUEST when issuing a # GetTransactionDetails response. :RECEIVEREMAIL => :receiver_email, :RECEIVERID => :receiver_id, :SUBJECT => :subject, :TRANSACTIONID => :transaction_id, :TRANSACTIONTYPE => :transaction_type, :PAYMENTTYPE => :payment_type, :ORDERTIME => :order_time, :PAYMENTSTATUS => :payment_status, :PENDINGREASON => :pending_reason, :REASONCODE => :reason_code, :PROTECTIONELIGIBILITY => :protection_eligibility, :PROTECTIONELIGIBILITYTYPE => :protection_eligibility_type, :ADDRESSOWNER => :address_owner, :ADDRESSSTATUS => :address_status, :INVNUM => :invoice_number, :CUSTOM => :custom } attr_accessor *@@attribute_mapping.values attr_accessor :shipping_options_is_default, :success_page_redirect_requested, :insurance_option_selected attr_accessor :amount, :description, :ship_to, :bill_to, :payer, :recurring, :billing_agreement, :refund attr_accessor :payment_responses, :payment_info, :items alias_method :colleration_id, :correlation_id # NOTE: I made a typo :p def initialize(attributes = {}) attrs = attributes.dup @@attribute_mapping.each do |key, value| self.send "#{value}=", attrs.delete(key) end @shipping_options_is_default = attrs.delete(:SHIPPINGOPTIONISDEFAULT) == 'true' @success_page_redirect_requested = attrs.delete(:SUCCESSPAGEREDIRECTREQUESTED) == 'true' @insurance_option_selected = attrs.delete(:INSURANCEOPTIONSELECTED) == 'true' @amount = Payment::Common::Amount.new( :total => attrs.delete(:AMT), :item => attrs.delete(:ITEMAMT), :handing => attrs.delete(:HANDLINGAMT), :insurance => attrs.delete(:INSURANCEAMT), :ship_disc => attrs.delete(:SHIPDISCAMT), :shipping => attrs.delete(:SHIPPINGAMT), :tax => attrs.delete(:TAXAMT), :fee => attrs.delete(:FEEAMT) ) @ship_to = Payment::Response::Address.new( :owner => attrs.delete(:SHIPADDRESSOWNER), :status => attrs.delete(:SHIPADDRESSSTATUS), :name => attrs.delete(:SHIPTONAME), :zip => attrs.delete(:SHIPTOZIP), :street => attrs.delete(:SHIPTOSTREET), :street2 => attrs.delete(:SHIPTOSTREET2), :city => attrs.delete(:SHIPTOCITY), :state => attrs.delete(:SHIPTOSTATE), :country_code => attrs.delete(:SHIPTOCOUNTRYCODE), :country_name => attrs.delete(:SHIPTOCOUNTRYNAME) ) @bill_to = Payment::Response::Address.new( :owner => attrs.delete(:ADDRESSID), :status => attrs.delete(:ADDRESSSTATUS), :name => attrs.delete(:BILLINGNAME), :zip => attrs.delete(:ZIP), :street => attrs.delete(:STREET), :street2 => attrs.delete(:STREET2), :city => attrs.delete(:CITY), :state => attrs.delete(:STATE), :country_code => attrs.delete(:COUNTRY) ) if attrs[:PAYERID] @payer = Payment::Response::Payer.new( :identifier => attrs.delete(:PAYERID), :status => attrs.delete(:PAYERSTATUS), :first_name => attrs.delete(:FIRSTNAME), :last_name => attrs.delete(:LASTNAME), :email => attrs.delete(:EMAIL), :company => attrs.delete(:BUSINESS) ) end if attrs[:PROFILEID] @recurring = Payment::Recurring.new( :identifier => attrs.delete(:PROFILEID), # NOTE: # CreateRecurringPaymentsProfile returns PROFILESTATUS # GetRecurringPaymentsProfileDetails returns STATUS :description => @description, :status => attrs.delete(:STATUS) || attrs.delete(:PROFILESTATUS), :start_date => attrs.delete(:PROFILESTARTDATE), :name => attrs.delete(:SUBSCRIBERNAME), :reference => attrs.delete(:PROFILEREFERENCE), :auto_bill => attrs.delete(:AUTOBILLOUTAMT), :max_fails => attrs.delete(:MAXFAILEDPAYMENTS), :aggregate_amount => attrs.delete(:AGGREGATEAMT), :aggregate_optional_amount => attrs.delete(:AGGREGATEOPTIONALAMT), :final_payment_date => attrs.delete(:FINALPAYMENTDUEDATE) ) if attrs[:BILLINGPERIOD] @recurring.billing = Payment::Recurring::Billing.new( :amount => @amount, :currency_code => @currency_code, :period => attrs.delete(:BILLINGPERIOD), :frequency => attrs.delete(:BILLINGFREQUENCY), :total_cycles => attrs.delete(:TOTALBILLINGCYCLES), :trial => { :period => attrs.delete(:TRIALBILLINGPERIOD), :frequency => attrs.delete(:TRIALBILLINGFREQUENCY), :total_cycles => attrs.delete(:TRIALTOTALBILLINGCYCLES), :currency_code => attrs.delete(:TRIALCURRENCYCODE), :amount => attrs.delete(:TRIALAMT), :tax_amount => attrs.delete(:TRIALTAXAMT), :shipping_amount => attrs.delete(:TRIALSHIPPINGAMT), :paid => attrs.delete(:TRIALAMTPAID) } ) end if attrs[:REGULARAMT] @recurring.regular_billing = Payment::Recurring::Billing.new( :amount => attrs.delete(:REGULARAMT), :shipping_amount => attrs.delete(:REGULARSHIPPINGAMT), :tax_amount => attrs.delete(:REGULARTAXAMT), :currency_code => attrs.delete(:REGULARCURRENCYCODE), :period => attrs.delete(:REGULARBILLINGPERIOD), :frequency => attrs.delete(:REGULARBILLINGFREQUENCY), :total_cycles => attrs.delete(:REGULARTOTALBILLINGCYCLES), :paid => attrs.delete(:REGULARAMTPAID) ) @recurring.summary = Payment::Recurring::Summary.new( :next_billing_date => attrs.delete(:NEXTBILLINGDATE), :cycles_completed => attrs.delete(:NUMCYCLESCOMPLETED), :cycles_remaining => attrs.delete(:NUMCYCLESREMAINING), :outstanding_balance => attrs.delete(:OUTSTANDINGBALANCE), :failed_count => attrs.delete(:FAILEDPAYMENTCOUNT), :last_payment_date => attrs.delete(:LASTPAYMENTDATE), :last_payment_amount => attrs.delete(:LASTPAYMENTAMT) ) end end if attrs[:BILLINGAGREEMENTID] @billing_agreement = Payment::Response::Reference.new( :identifier => attrs.delete(:BILLINGAGREEMENTID), :description => attrs.delete(:BILLINGAGREEMENTDESCRIPTION), :status => attrs.delete(:BILLINGAGREEMENTSTATUS) ) billing_agreement_info = Payment::Response::Info.attribute_mapping.keys.inject({}) do |billing_agreement_info, key| billing_agreement_info.merge! key => attrs.delete(key) end @billing_agreement.info = Payment::Response::Info.new billing_agreement_info @billing_agreement.info.amount = @amount end if attrs[:REFUNDTRANSACTIONID] @refund = Payment::Response::Refund.new( :transaction_id => attrs.delete(:REFUNDTRANSACTIONID), :amount => { :total => attrs.delete(:TOTALREFUNDEDAMOUNT), :fee => attrs.delete(:FEEREFUNDAMT), :gross => attrs.delete(:GROSSREFUNDAMT), :net => attrs.delete(:NETREFUNDAMT) } ) end # payment_responses payment_responses = [] attrs.keys.each do |_attr_| prefix, index, key = case _attr_.to_s when /^PAYMENTREQUEST/, /^PAYMENTREQUESTINFO/ _attr_.to_s.split('_') when /^L_PAYMENTREQUEST/ _attr_.to_s.split('_')[1..-1] end if prefix payment_responses[index.to_i] ||= {} payment_responses[index.to_i][key.to_sym] = attrs.delete(_attr_) end end @payment_responses = payment_responses.collect do |_attrs_| Payment::Response.new _attrs_ end # payment_info payment_info = [] attrs.keys.each do |_attr_| prefix, index, key = _attr_.to_s.split('_') if prefix == 'PAYMENTINFO' payment_info[index.to_i] ||= {} payment_info[index.to_i][key.to_sym] = attrs.delete(_attr_) end end @payment_info = payment_info.collect do |_attrs_| Payment::Response::Info.new _attrs_ end # payment_info items = [] attrs.keys.each do |_attr_| key, index = _attr_.to_s.scan(/^L_(.+?)(\d+)$/).flatten if index items[index.to_i] ||= {} items[index.to_i][key.to_sym] = attrs.delete(_attr_) end end @items = items.collect do |_attrs_| Payment::Response::Item.new _attrs_ end # remove duplicated parameters attrs.delete(:SHIPTOCOUNTRY) # NOTE: Same with SHIPTOCOUNTRYCODE attrs.delete(:SALESTAX) # Same as TAXAMT # warn ignored attrs attrs.each do |key, value| Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn end end end end end ================================================ FILE: lib/paypal/payment/common/amount.rb ================================================ module Paypal module Payment module Common class Amount < Base attr_optional :total, :item, :fee, :handing, :insurance, :ship_disc, :shipping, :tax, :gross, :net def numeric_attribute?(key) true end end end end end ================================================ FILE: lib/paypal/payment/recurring/activation.rb ================================================ module Paypal module Payment class Recurring::Activation < Base attr_optional :initial_amount, :failed_action def to_params { :INITAMT => Util.formatted_amount(self.initial_amount), :FAILEDINITAMTACTION => self.failed_action } end end end end ================================================ FILE: lib/paypal/payment/recurring/billing.rb ================================================ module Paypal module Payment class Recurring::Billing < Base attr_optional :period, :frequency, :paid, :currency_code, :total_cycles attr_accessor :amount, :trial def initialize(attributes = {}) @amount = if attributes[:amount].is_a?(Common::Amount) attributes[:amount] else Common::Amount.new( :total => attributes[:amount], :tax => attributes[:tax_amount], :shipping => attributes[:shipping_amount] ) end @trial = Recurring::Billing.new(attributes[:trial]) if attributes[:trial].present? super end def to_params trial_params = (trial.try(:to_params) || {}).inject({}) do |trial_params, (key, value)| trial_params.merge( :"TRIAL#{key}" => value ) end trial_params.merge( :BILLINGPERIOD => self.period, :BILLINGFREQUENCY => self.frequency, :TOTALBILLINGCYCLES => self.total_cycles, :AMT => Util.formatted_amount(self.amount.total), :CURRENCYCODE => self.currency_code, :SHIPPINGAMT => Util.formatted_amount(self.amount.shipping), :TAXAMT => Util.formatted_amount(self.amount.tax) ) end end end end ================================================ FILE: lib/paypal/payment/recurring/summary.rb ================================================ module Paypal module Payment class Recurring::Summary < Base attr_optional :next_billing_date, :cycles_completed, :cycles_remaining, :outstanding_balance, :failed_count, :last_payment_date, :last_payment_amount def numeric_attribute?(key) super || [:outstanding_balance, :failed_count].include?(key) end end end end ================================================ FILE: lib/paypal/payment/recurring.rb ================================================ module Paypal module Payment class Recurring < Base attr_optional :start_date, :description, :identifier, :status, :name, :reference, :max_fails, :auto_bill, :aggregate_amount, :aggregate_optional_amount, :final_payment_date attr_accessor :activation, :billing, :regular_billing, :summary def initialize(attributes = {}) super @activation = Activation.new attributes[:activation] if attributes[:activation] @billing = Billing.new attributes[:billing] if attributes[:billing] @regular_billing = Billing.new attributes[:regular_billing] if attributes[:regular_billing] @summary = Summary.new attributes[:summary] if attributes[:summary] end def to_params params = [ self.billing, self.activation ].compact.inject({}) do |params, attribute| params.merge! attribute.to_params end if self.start_date.is_a?(Time) self.start_date = self.start_date.to_s(:db) end params.merge!( :DESC => self.description, :MAXFAILEDPAYMENTS => self.max_fails, :AUTOBILLOUTAMT => self.auto_bill, :PROFILESTARTDATE => self.start_date, :SUBSCRIBERNAME => self.name, :PROFILEREFERENCE => self.reference ) params.delete_if do |k, v| v.blank? end end def numeric_attribute?(key) super || [:max_fails, :failed_count].include?(key) end end end end ================================================ FILE: lib/paypal/payment/request/item.rb ================================================ module Paypal module Payment class Request::Item < Base attr_optional :name, :description, :amount, :number, :quantity, :category, :url def initialize(attributes = {}) super @quantity ||= 1 end def to_params(parent_index, index = 0) { :"L_PAYMENTREQUEST_#{parent_index}_NAME#{index}" => self.name, :"L_PAYMENTREQUEST_#{parent_index}_DESC#{index}" => self.description, :"L_PAYMENTREQUEST_#{parent_index}_AMT#{index}" => Util.formatted_amount(self.amount), :"L_PAYMENTREQUEST_#{parent_index}_NUMBER#{index}" => self.number, :"L_PAYMENTREQUEST_#{parent_index}_QTY#{index}" => self.quantity, :"L_PAYMENTREQUEST_#{parent_index}_ITEMCATEGORY#{index}" => self.category, :"L_PAYMENTREQUEST_#{parent_index}_ITEMURL#{index}" => self.url }.delete_if do |k, v| v.blank? end end end end end ================================================ FILE: lib/paypal/payment/request.rb ================================================ module Paypal module Payment class Request < Base attr_optional :action, :currency_code, :description, :notify_url, :billing_type, :billing_agreement_description, :billing_agreement_id, :request_id, :seller_id, :invoice_number, :custom attr_accessor :amount, :items, :custom_fields def initialize(attributes = {}) @amount = if attributes[:amount].is_a?(Common::Amount) attributes[:amount] else Common::Amount.new( :total => attributes[:amount], :tax => attributes[:tax_amount], :shipping => attributes[:shipping_amount] ) end @items = [] Array(attributes[:items]).each do |item_attrs| @items << Item.new(item_attrs) end @custom_fields = attributes[:custom_fields] || {} super end def to_params(index = 0) params = { :"PAYMENTREQUEST_#{index}_PAYMENTACTION" => self.action, :"PAYMENTREQUEST_#{index}_AMT" => Util.formatted_amount(self.amount.total), :"PAYMENTREQUEST_#{index}_TAXAMT" => Util.formatted_amount(self.amount.tax), :"PAYMENTREQUEST_#{index}_SHIPPINGAMT" => Util.formatted_amount(self.amount.shipping), :"PAYMENTREQUEST_#{index}_CURRENCYCODE" => self.currency_code, :"PAYMENTREQUEST_#{index}_DESC" => self.description, :"PAYMENTREQUEST_#{index}_INVNUM" => self.invoice_number, :"PAYMENTREQUEST_#{index}_CUSTOM" => self.custom, # NOTE: # notify_url works only when DoExpressCheckoutPayment called. # recurring payment doesn't support dynamic notify_url. :"PAYMENTREQUEST_#{index}_NOTIFYURL" => self.notify_url, :"L_BILLINGTYPE#{index}" => self.billing_type, :"L_BILLINGAGREEMENTDESCRIPTION#{index}" => self.billing_agreement_description, # FOR PARALLEL PAYMENT :"PAYMENTREQUEST_#{index}_PAYMENTREQUESTID" => self.request_id, :"PAYMENTREQUEST_#{index}_SELLERPAYPALACCOUNTID" => self.seller_id }.delete_if do |k, v| v.blank? end if self.items.present? params[:"PAYMENTREQUEST_#{index}_ITEMAMT"] = Util.formatted_amount(self.items_amount) self.items.each_with_index do |item, item_index| params.merge! item.to_params(index, item_index) end end self.custom_fields.each do |key, value| field = key.to_s.upcase.gsub("{N}", index.to_s).to_sym params[field] = value end params end def items_amount self.items.sum do |item| item.quantity * BigDecimal.new(item.amount.to_s) end end end end end ================================================ FILE: lib/paypal/payment/response/address.rb ================================================ module Paypal module Payment class Response::Address < Base attr_optional :owner, :status, :name, :zip, :street, :street2, :city, :state, :country_code, :country_name end end end ================================================ FILE: lib/paypal/payment/response/info.rb ================================================ module Paypal module Payment class Response::Info < Base cattr_reader :attribute_mapping @@attribute_mapping = { :ACK => :ack, :CURRENCYCODE => :currency_code, :ERRORCODE => :error_code, :ORDERTIME => :order_time, :PAYMENTSTATUS => :payment_status, :PAYMENTTYPE => :payment_type, :PENDINGREASON => :pending_reason, :PROTECTIONELIGIBILITY => :protection_eligibility, :PROTECTIONELIGIBILITYTYPE => :protection_eligibility_type, :REASONCODE => :reason_code, :RECEIPTID => :receipt_id, :SECUREMERCHANTACCOUNTID => :secure_merchant_account_id, :TRANSACTIONID => :transaction_id, :TRANSACTIONTYPE => :transaction_type, :PAYMENTREQUESTID => :request_id, :SELLERPAYPALACCOUNTID => :seller_id, :EXCHANGERATE => :exchange_rate } attr_accessor *@@attribute_mapping.values attr_accessor :amount def initialize(attributes = {}) attrs = attributes.dup @@attribute_mapping.each do |key, value| self.send "#{value}=", attrs.delete(key) end @amount = Common::Amount.new( :total => attrs.delete(:AMT), :fee => attrs.delete(:FEEAMT), :tax => attrs.delete(:TAXAMT) ) # warn ignored params attrs.each do |key, value| Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn end end end end end ================================================ FILE: lib/paypal/payment/response/item.rb ================================================ module Paypal module Payment class Response::Item < Base cattr_reader :attribute_mapping @@attribute_mapping = { :NAME => :name, :DESC => :description, :QTY => :quantity, :NUMBER => :number, :ITEMCATEGORY => :category, :ITEMWIDTHVALUE => :width, :ITEMHEIGHTVALUE => :height, :ITEMLENGTHVALUE => :length, :ITEMWEIGHTVALUE => :weight, :SHIPPINGAMT => :shipping, :HANDLINGAMT => :handling, :CURRENCYCODE => :currency } attr_accessor *@@attribute_mapping.values attr_accessor :amount def initialize(attributes = {}) attrs = attributes.dup @@attribute_mapping.each do |key, value| self.send "#{value}=", attrs.delete(key) end @quantity = @quantity.to_i @amount = Common::Amount.new( :total => attrs.delete(:AMT), :tax => attrs.delete(:TAXAMT) ) # warn ignored params attrs.each do |key, value| Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn end end end end end ================================================ FILE: lib/paypal/payment/response/payer.rb ================================================ module Paypal module Payment class Response::Payer < Base attr_optional :identifier, :status, :first_name, :last_name, :email, :company end end end ================================================ FILE: lib/paypal/payment/response/reference.rb ================================================ module Paypal module Payment class Response::Reference < Base attr_required :identifier attr_optional :description, :status attr_accessor :info end end end ================================================ FILE: lib/paypal/payment/response/refund.rb ================================================ module Paypal module Payment class Response::Refund < Base attr_optional :transaction_id attr_accessor :amount def initialize(attributes = {}) super @amount = Common::Amount.new(attributes[:amount]) end end end end ================================================ FILE: lib/paypal/payment/response.rb ================================================ module Paypal module Payment class Response < Base attr_accessor :amount, :ship_to, :bill_to, :description, :note, :items, :notify_url, :insurance_option_offered, :currency_code, :short_message, :long_message, :error_code, :severity_code, :ack, :transaction_id, :billing_agreement_id, :request_id, :seller_id def initialize(attributes = {}) attrs = attributes.dup @amount = Common::Amount.new( :total => attrs.delete(:AMT), :item => attrs.delete(:ITEMAMT), :handing => attrs.delete(:HANDLINGAMT), :insurance => attrs.delete(:INSURANCEAMT), :ship_disc => attrs.delete(:SHIPDISCAMT), :shipping => attrs.delete(:SHIPPINGAMT), :tax => attrs.delete(:TAXAMT) ) @ship_to = Payment::Response::Address.new( :name => attrs.delete(:SHIPTONAME), :zip => attrs.delete(:SHIPTOZIP), :street => attrs.delete(:SHIPTOSTREET), :street2 => attrs.delete(:SHIPTOSTREET2), :city => attrs.delete(:SHIPTOCITY), :state => attrs.delete(:SHIPTOSTATE), :country_code => attrs.delete(:SHIPTOCOUNTRYCODE), :country_name => attrs.delete(:SHIPTOCOUNTRYNAME) ) @bill_to = Payment::Response::Address.new( :owner => attrs.delete(:ADDRESSID), :status => attrs.delete(:ADDRESSSTATUS), :name => attrs.delete(:BILLINGNAME), :zip => attrs.delete(:ZIP), :street => attrs.delete(:STREET), :street2 => attrs.delete(:STREET2), :city => attrs.delete(:CITY), :state => attrs.delete(:STATE), :country_code => attrs.delete(:COUNTRY) ) @description = attrs.delete(:DESC) @note = attrs.delete(:NOTETEXT) @notify_url = attrs.delete(:NOTIFYURL) @insurance_option_offered = attrs.delete(:INSURANCEOPTIONOFFERED) == 'true' @currency_code = attrs.delete(:CURRENCYCODE) @short_message = attrs.delete(:SHORTMESSAGE) @long_message = attrs.delete(:LONGMESSAGE) @error_code = attrs.delete(:ERRORCODE) @severity_code = attrs.delete(:SEVERITYCODE) @ack = attrs.delete(:ACK) @transaction_id = attrs.delete(:TRANSACTIONID) @billing_agreement_id = attrs.delete(:BILLINGAGREEMENTID) @request_id = attrs.delete(:PAYMENTREQUESTID) @seller_id = attrs.delete(:SELLERPAYPALACCOUNTID) # items items = [] attrs.keys.each do |_attr_| key, index = _attr_.to_s.scan(/^(.+?)(\d+)$/).flatten if index items[index.to_i] ||= {} items[index.to_i][key.to_sym] = attrs.delete(:"#{key}#{index}") end end @items = items.collect do |_attr_| Item.new(_attr_) end # warn ignored params attrs.each do |key, value| Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn end end end end end ================================================ FILE: lib/paypal/util.rb ================================================ module Paypal module Util def self.formatted_amount(x) # Thanks @nahi ;) sprintf "%0.2f", BigDecimal.new(x.to_s).truncate(2) end def self.to_numeric(x) if x.to_f == x.to_i x.to_i else x.to_f end end def numeric_attribute?(key) !!(key.to_s =~ /(amount|frequency|cycles|paid)/) end def ==(other) instance_variables.all? do |key| instance_variable_get(key) == other.instance_variable_get(key) end end end end ================================================ FILE: lib/paypal.rb ================================================ require 'logger' require 'active_support' require 'active_support/core_ext' require 'attr_required' require 'attr_optional' require 'rest_client' module Paypal mattr_accessor :api_version self.api_version = '88.0' ENDPOINT = { :production => 'https://www.paypal.com/cgi-bin/webscr', :sandbox => 'https://www.sandbox.paypal.com/cgi-bin/webscr' } POPUP_ENDPOINT = { :production => 'https://www.paypal.com/incontext', :sandbox => 'https://www.sandbox.paypal.com/incontext' } def self.endpoint if sandbox? Paypal::ENDPOINT[:sandbox] else Paypal::ENDPOINT[:production] end end def self.popup_endpoint if sandbox? Paypal::POPUP_ENDPOINT[:sandbox] else Paypal::POPUP_ENDPOINT[:production] end end def self.log(message, mode = :info) self.logger.send mode, message end def self.logger @@logger end def self.logger=(logger) @@logger = logger end @@logger = Logger.new(STDERR) @@logger.progname = 'Paypal::Express' def self.sandbox? @@sandbox end def self.sandbox! self.sandbox = true end def self.sandbox=(boolean) @@sandbox = boolean end self.sandbox = false end require 'paypal/util' require 'paypal/exception' require 'paypal/exception/http_error' require 'paypal/exception/api_error' require 'paypal/base' require 'paypal/ipn' require 'paypal/nvp/request' require 'paypal/nvp/response' require 'paypal/payment/common/amount' require 'paypal/express/request' require 'paypal/express/response' require 'paypal/payment/request' require 'paypal/payment/request/item' require 'paypal/payment/response' require 'paypal/payment/response/info' require 'paypal/payment/response/item' require 'paypal/payment/response/payer' require 'paypal/payment/response/reference' require 'paypal/payment/response/refund' require 'paypal/payment/response/address' require 'paypal/payment/recurring' require 'paypal/payment/recurring/activation' require 'paypal/payment/recurring/billing' require 'paypal/payment/recurring/summary' ================================================ FILE: paypal-express.gemspec ================================================ Gem::Specification.new do |s| s.name = "paypal-express" s.version = File.read(File.join(File.dirname(__FILE__), "VERSION")) s.required_rubygems_version = Gem::Requirement.new(">= 1.3.6") if s.respond_to? :required_rubygems_version= s.authors = ["nov matake"] s.description = %q{PayPal Express Checkout API Client for Instance, Recurring and Digital Goods Payment.} s.summary = %q{PayPal Express Checkout API Client for Instance, Recurring and Digital Goods Payment.} s.email = "nov@matake.jp" s.extra_rdoc_files = ["LICENSE", "README.rdoc"] s.rdoc_options = ["--charset=UTF-8"] s.homepage = "http://github.com/nov/paypal-express" s.require_paths = ["lib"] s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.add_dependency "activesupport", ">= 2.3" s.add_dependency "rest-client" s.add_dependency "attr_required", ">= 0.0.5" s.add_development_dependency "rake", ">= 0.8" s.add_development_dependency "simplecov" s.add_development_dependency "rspec", "< 2.99" s.add_development_dependency "fakeweb", ">= 1.3.0" end ================================================ FILE: spec/fake_response/BillAgreementUpdate/fetch.txt ================================================ BILLINGAGREEMENTID=B%2d8XW15926RT2253736&BILLINGAGREEMENTDESCRIPTION=Reference%20Payment%20Agreement&BILLINGAGREEMENTSTATUS=Canceled&TIMESTAMP=2011%2d08%2d06T09%3a47%3a41Z&CORRELATIONID=28e5a24b3ea98&ACK=Success&VERSION=78%2e0&BUILD=2020243&EMAIL=client_1305381804_per%40matake%2ejp&PAYERID=CMJLWADKVFQYJ&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US ================================================ FILE: spec/fake_response/BillAgreementUpdate/revoke.txt ================================================ BILLINGAGREEMENTID=B%2d8XW15926RT2253736&BILLINGAGREEMENTDESCRIPTION=Reference%20Payment%20Agreement&BILLINGAGREEMENTSTATUS=Canceled&TIMESTAMP=2011%2d08%2d06T09%3a47%3a41Z&CORRELATIONID=28e5a24b3ea98&ACK=Success&VERSION=78%2e0&BUILD=2020243&EMAIL=client_1305381804_per%40matake%2ejp&PAYERID=CMJLWADKVFQYJ&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US ================================================ FILE: spec/fake_response/CreateBillingAgreement/success.txt ================================================ BILLINGAGREEMENTID=B%2d45776062X95244343&TIMESTAMP=2011%2d08%2d06T07%3a53%3a50Z&CORRELATIONID=9a16fde4456e1&ACK=Success&VERSION=78%2e0&BUILD=2020243 ================================================ FILE: spec/fake_response/CreateRecurringPaymentsProfile/failure.txt ================================================ TIMESTAMP=2011%2d02%2d08T07%3a18%3a29Z&CORRELATIONID=fde1527b25b4f&ACK=Failure&VERSION=66%2e0&BUILD=1704252&L_ERRORCODE0=11502&L_SHORTMESSAGE0=Invalid%20Token&L_LONGMESSAGE0=The%20token%20is%20invalid&L_SEVERITYCODE0=Error ================================================ FILE: spec/fake_response/CreateRecurringPaymentsProfile/success.txt ================================================ PROFILEID=I%2dL8N58XFUCET3&PROFILESTATUS=ActiveProfile&TIMESTAMP=2011%2d02%2d08T07%3a17%3a51Z&CORRELATIONID=7d2e125f5ca63&ACK=Success&VERSION=66%2e0&BUILD=1704252 ================================================ FILE: spec/fake_response/DoCapture/failure.txt ================================================ ACK=Failure&BUILD=8334781&CORRELATIONID=dd9e16358819b&L_ERRORCODE0=10609&L_LONGMESSAGE0=Transaction+id+is+invalid.&L_SEVERITYCODE0=Error&L_SHORTMESSAGE0=Invalid+transactionID.&TIMESTAMP=2013-11-05T17%3A43%3A56Z&VERSION=88.0 ================================================ FILE: spec/fake_response/DoCapture/success.txt ================================================ ACK=Success&AMT=440.00&AUTHORIZATIONID=2RG78938NK8989844&BUILD=8334781&CORRELATIONID=311611c111b48&CURRENCYCODE=BRL&EXCHANGERATE=0.426354&FEEAMT=13.16&ORDERTIME=2013-11-05T17%3A34%3A56Z&PARENTTRANSACTIONID=2RG78938NK8989844&PAYMENTSTATUS=Completed&PAYMENTTYPE=instant&PENDINGREASON=None&PROTECTIONELIGIBILITY=Ineligible&PROTECTIONELIGIBILITYTYPE=None&REASONCODE=None&SETTLEAMT=181.98&TAXAMT=0.00&TIMESTAMP=2013-11-05T17%3A34%3A56Z&TRANSACTIONID=9VW4495267708531S&TRANSACTIONTYPE=expresscheckout&VERSION=88.0 ================================================ FILE: spec/fake_response/DoExpressCheckoutPayment/failure.txt ================================================ TIMESTAMP=2011%2d02%2d08T07%3a15%3a19Z&CORRELATIONID=5dd0308345382&ACK=Failure&VERSION=66%2e0&BUILD=1721431&L_ERRORCODE0=10410&L_SHORTMESSAGE0=Invalid%20token&L_LONGMESSAGE0=Invalid%20token%2e&L_SEVERITYCODE0=Error ================================================ FILE: spec/fake_response/DoExpressCheckoutPayment/success.txt ================================================ TOKEN=EC-9E2743126S4330617&SUCCESSPAGEREDIRECTREQUESTED=false&TIMESTAMP=2011-02-08T03:23:55Z&CORRELATIONID=15b93874c358c&ACK=Success&VERSION=66.0&BUILD=1721431&INSURANCEOPTIONSELECTED=false&SHIPPINGOPTIONISDEFAULT=false&PAYMENTINFO_0_TRANSACTIONID=8NC65222871997739&PAYMENTINFO_0_TRANSACTIONTYPE=expresscheckout&PAYMENTINFO_0_PAYMENTTYPE=instant&PAYMENTINFO_0_ORDERTIME=2011-02-08T03:23:54Z&PAYMENTINFO_0_AMT=14.00&PAYMENTINFO_0_FEEAMT=0.85&PAYMENTINFO_0_TAXAMT=0.00&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_ERRORCODE=0&PAYMENTINFO_0_ACK=Success ================================================ FILE: spec/fake_response/DoExpressCheckoutPayment/success_with_billing_agreement.txt ================================================ TOKEN=EC-9E2743126S4330617&SUCCESSPAGEREDIRECTREQUESTED=false&TIMESTAMP=2011-02-08T03:23:55Z&CORRELATIONID=15b93874c358c&ACK=Success&VERSION=66.0&BUILD=1721431&INSURANCEOPTIONSELECTED=false&SHIPPINGOPTIONISDEFAULT=false&PAYMENTINFO_0_TRANSACTIONID=8NC65222871997739&PAYMENTINFO_0_TRANSACTIONTYPE=expresscheckout&PAYMENTINFO_0_PAYMENTTYPE=instant&PAYMENTINFO_0_ORDERTIME=2011-02-08T03:23:54Z&PAYMENTINFO_0_AMT=14.00&PAYMENTINFO_0_FEEAMT=0.85&PAYMENTINFO_0_TAXAMT=0.00&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_ERRORCODE=0&BILLINGAGREEMENTID=B-1XR87946TC504770W&PAYMENTINFO_0_ACK=Success ================================================ FILE: spec/fake_response/DoExpressCheckoutPayment/success_with_many_items.txt ================================================ TOKEN=EC%2d7HG122968U843864S&CHECKOUTSTATUS=PaymentActionNotInitiated&TIMESTAMP=2011%2d04%2d12T03%3a51%3a58Z&CORRELATIONID=6a0a92ea79ac3&ACK=Success&VERSION=69%2e0&BUILD=1824201&EMAIL=buyer_1297999636_per%40cerego%2ecom&PAYERID=9RWDTMRKKHQ8S&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US&CURRENCYCODE=USD&AMT=1000&ITEMAMT=1000&SHIPPINGAMT=0&HANDLINGAMT=0&TAXAMT=0&DESC=Instant%20Payment%20Request&NOTIFYURL=http%3a%2f%2fmerchant%2eexample%2ecom%2fnotify&INSURANCEAMT=0&SHIPDISCAMT=0&L_ITEMHEIGHTVALUE0=%20%20%200%2e00000&L_ITEMHEIGHTVALUE1=%20%20%200%2e00000&L_ITEMHEIGHTVALUE2=%20%20%200%2e00000&L_ITEMHEIGHTVALUE3=%20%20%200%2e00000&L_ITEMHEIGHTVALUE4=%20%20%200%2e00000&L_ITEMHEIGHTVALUE5=%20%20%200%2e00000&L_ITEMHEIGHTVALUE6=%20%20%200%2e00000&L_ITEMHEIGHTVALUE7=%20%20%200%2e00000&L_ITEMHEIGHTVALUE8=%20%20%200%2e00000&L_ITEMHEIGHTVALUE9=%20%20%200%2e00000&L_ITEMHEIGHTVALUE10=%20%20%200%2e00000&L_ITEMHEIGHTVALUE11=%20%20%200%2e00000&L_ITEMHEIGHTVALUE12=%20%20%200%2e00000&L_ITEMHEIGHTVALUE13=%20%20%200%2e00000&L_ITEMHEIGHTVALUE14=%20%20%200%2e00000&L_ITEMHEIGHTVALUE15=%20%20%200%2e00000&L_ITEMHEIGHTVALUE16=%20%20%200%2e00000&L_ITEMHEIGHTVALUE17=%20%20%200%2e00000&L_ITEMHEIGHTVALUE18=%20%20%200%2e00000&L_ITEMHEIGHTVALUE19=%20%20%200%2e00000&L_ITEMWIDTHVALUE0=%20%20%200%2e00000&L_ITEMWIDTHVALUE1=%20%20%200%2e00000&L_ITEMWIDTHVALUE2=%20%20%200%2e00000&L_ITEMWIDTHVALUE3=%20%20%200%2e00000&L_ITEMWIDTHVALUE4=%20%20%200%2e00000&L_ITEMWIDTHVALUE5=%20%20%200%2e00000&L_ITEMWIDTHVALUE6=%20%20%200%2e00000&L_ITEMWIDTHVALUE7=%20%20%200%2e00000&L_ITEMWIDTHVALUE8=%20%20%200%2e00000&L_ITEMWIDTHVALUE9=%20%20%200%2e00000&L_ITEMWIDTHVALUE10=%20%20%200%2e00000&L_ITEMWIDTHVALUE11=%20%20%200%2e00000&L_ITEMWIDTHVALUE12=%20%20%200%2e00000&L_ITEMWIDTHVALUE13=%20%20%200%2e00000&L_ITEMWIDTHVALUE14=%20%20%200%2e00000&L_ITEMWIDTHVALUE15=%20%20%200%2e00000&L_ITEMWIDTHVALUE16=%20%20%200%2e00000&L_ITEMWIDTHVALUE17=%20%20%200%2e00000&L_ITEMWIDTHVALUE18=%20%20%200%2e00000&L_ITEMWIDTHVALUE19=%20%20%200%2e00000&L_ITEMLENGTHVALUE0=%20%20%200%2e00000&L_ITEMLENGTHVALUE1=%20%20%200%2e00000&L_ITEMLENGTHVALUE2=%20%20%200%2e00000&L_ITEMLENGTHVALUE3=%20%20%200%2e00000&L_ITEMLENGTHVALUE4=%20%20%200%2e00000&L_ITEMLENGTHVALUE5=%20%20%200%2e00000&L_ITEMLENGTHVALUE6=%20%20%200%2e00000&L_ITEMLENGTHVALUE7=%20%20%200%2e00000&L_ITEMLENGTHVALUE8=%20%20%200%2e00000&L_ITEMLENGTHVALUE9=%20%20%200%2e00000&L_ITEMLENGTHVALUE10=%20%20%200%2e00000&L_ITEMLENGTHVALUE11=%20%20%200%2e00000&L_ITEMLENGTHVALUE12=%20%20%200%2e00000&L_ITEMLENGTHVALUE13=%20%20%200%2e00000&L_ITEMLENGTHVALUE14=%20%20%200%2e00000&L_ITEMLENGTHVALUE15=%20%20%200%2e00000&L_ITEMLENGTHVALUE16=%20%20%200%2e00000&L_ITEMLENGTHVALUE17=%20%20%200%2e00000&L_ITEMLENGTHVALUE18=%20%20%200%2e00000&L_ITEMLENGTHVALUE19=%20%20%200%2e00000&L_NAME0=Item0&L_NAME1=Item1&L_NAME2=Item2&L_NAME3=Item3&L_NAME4=Item4&L_NAME5=Item5&L_NAME6=Item6&L_NAME7=Item7&L_NAME8=Item8&L_NAME9=Item9&L_NAME10=Item10&L_NAME11=Item11&L_NAME12=Item12&L_NAME13=Item13&L_NAME14=Item14&L_NAME15=Item15&L_NAME16=Item16&L_NAME17=Item17&L_NAME18=Item18&L_NAME19=Item19&L_ITEMWEIGHTVALUE0=%20%20%200%2e00000&L_ITEMWEIGHTVALUE1=%20%20%200%2e00000&L_ITEMWEIGHTVALUE2=%20%20%200%2e00000&L_ITEMWEIGHTVALUE3=%20%20%200%2e00000&L_ITEMWEIGHTVALUE4=%20%20%200%2e00000&L_ITEMWEIGHTVALUE5=%20%20%200%2e00000&L_ITEMWEIGHTVALUE6=%20%20%200%2e00000&L_ITEMWEIGHTVALUE7=%20%20%200%2e00000&L_ITEMWEIGHTVALUE8=%20%20%200%2e00000&L_ITEMWEIGHTVALUE9=%20%20%200%2e00000&L_ITEMWEIGHTVALUE10=%20%20%200%2e00000&L_ITEMWEIGHTVALUE11=%20%20%200%2e00000&L_ITEMWEIGHTVALUE12=%20%20%200%2e00000&L_ITEMWEIGHTVALUE13=%20%20%200%2e00000&L_ITEMWEIGHTVALUE14=%20%20%200%2e00000&L_ITEMWEIGHTVALUE15=%20%20%200%2e00000&L_ITEMWEIGHTVALUE16=%20%20%200%2e00000&L_ITEMWEIGHTVALUE17=%20%20%200%2e00000&L_ITEMWEIGHTVALUE18=%20%20%200%2e00000&L_ITEMWEIGHTVALUE19=%20%20%200%2e00000&L_TAXAMT0=%20%20%200%2e00000&L_TAXAMT1=%20%20%200%2e00000&L_TAXAMT2=%20%20%200%2e00000&L_TAXAMT3=%20%20%200%2e00000&L_TAXAMT4=%20%20%200%2e00000&L_TAXAMT5=%20%20%200%2e00000&L_TAXAMT6=%20%20%200%2e00000&L_TAXAMT7=%20%20%200%2e00000&L_TAXAMT8=%20%20%200%2e00000&L_TAXAMT9=%20%20%200%2e00000&L_TAXAMT10=%20%20%200%2e00000&L_TAXAMT11=%20%20%200%2e00000&L_TAXAMT12=%20%20%200%2e00000&L_TAXAMT13=%20%20%200%2e00000&L_TAXAMT14=%20%20%200%2e00000&L_TAXAMT15=%20%20%200%2e00000&L_TAXAMT16=%20%20%200%2e00000&L_TAXAMT17=%20%20%200%2e00000&L_TAXAMT18=%20%20%200%2e00000&L_TAXAMT19=%20%20%200%2e00000&L_DESC0=A%20new%20Item%200&L_DESC1=A%20new%20Item%201&L_DESC2=A%20new%20Item%202&L_DESC3=A%20new%20Item%203&L_DESC4=A%20new%20Item%204&L_DESC5=A%20new%20Item%205&L_DESC6=A%20new%20Item%206&L_DESC7=A%20new%20Item%207&L_DESC8=A%20new%20Item%208&L_DESC9=A%20new%20Item%209&L_DESC10=A%20new%20Item%2010&L_DESC11=A%20new%20Item%2011&L_DESC12=A%20new%20Item%2012&L_DESC13=A%20new%20Item%2013&L_DESC14=A%20new%20Item%2014&L_DESC15=A%20new%20Item%2015&L_DESC16=A%20new%20Item%2016&L_DESC17=A%20new%20Item%2017&L_DESC18=A%20new%20Item%2018&L_DESC19=A%20new%20Item%2019&L_AMT0=50&L_AMT1=50&L_AMT2=50&L_AMT3=50&L_AMT4=50&L_AMT5=50&L_AMT6=50&L_AMT7=50&L_AMT8=50&L_AMT9=50&L_AMT10=50&L_AMT11=50&L_AMT12=50&L_AMT13=50&L_AMT14=50&L_AMT15=50&L_AMT16=50&L_AMT17=50&L_AMT18=50&L_AMT19=50&L_QTY0=1&L_QTY1=1&L_QTY2=1&L_QTY3=1&L_QTY4=1&L_QTY5=1&L_QTY6=1&L_QTY7=1&L_QTY8=1&L_QTY9=1&L_QTY10=1&L_QTY11=1&L_QTY12=1&L_QTY13=1&L_QTY14=1&L_QTY15=1&L_QTY16=1&L_QTY17=1&L_QTY18=1&L_QTY19=1&PAYMENTREQUEST_0_CURRENCYCODE=JPY&PAYMENTREQUEST_0_AMT=10&PAYMENTREQUEST_0_ITEMAMT=10&PAYMENTREQUEST_0_SHIPPINGAMT=0&PAYMENTREQUEST_0_HANDLINGAMT=0&PAYMENTREQUEST_0_TAXAMT=0&PAYMENTREQUEST_0_DESC=Instant%20Payment%20Request&PAYMENTREQUEST_0_NOTIFYURL=http%3a%2f%2fmerchant%2eexample%2ecom%2fnotify&PAYMENTREQUEST_0_INSURANCEAMT=0&PAYMENTREQUEST_0_SHIPDISCAMT=0&PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED=false&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE1=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE2=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE3=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE4=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE5=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE6=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE7=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE8=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE9=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE10=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE11=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE12=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE13=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE14=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE15=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE16=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE17=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE18=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE19=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE1=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE2=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE3=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE4=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE5=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE6=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE7=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE8=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE9=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE10=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE11=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE12=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE13=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE14=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE15=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE16=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE17=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE18=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE19=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE1=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE2=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE3=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE4=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE5=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE6=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE7=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE8=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE9=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE10=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE11=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE12=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE13=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE14=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE15=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE16=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE17=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE18=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE19=%20%20%200%2e00000&L_PAYMENTREQUEST_0_NAME0=Item0&L_PAYMENTREQUEST_0_NAME1=Item1&L_PAYMENTREQUEST_0_NAME2=Item2&L_PAYMENTREQUEST_0_NAME3=Item3&L_PAYMENTREQUEST_0_NAME4=Item4&L_PAYMENTREQUEST_0_NAME5=Item5&L_PAYMENTREQUEST_0_NAME6=Item6&L_PAYMENTREQUEST_0_NAME7=Item7&L_PAYMENTREQUEST_0_NAME8=Item8&L_PAYMENTREQUEST_0_NAME9=Item9&L_PAYMENTREQUEST_0_NAME10=Item10&L_PAYMENTREQUEST_0_NAME11=Item11&L_PAYMENTREQUEST_0_NAME12=Item12&L_PAYMENTREQUEST_0_NAME13=Item13&L_PAYMENTREQUEST_0_NAME14=Item14&L_PAYMENTREQUEST_0_NAME15=Item15&L_PAYMENTREQUEST_0_NAME16=Item16&L_PAYMENTREQUEST_0_NAME17=Item17&L_PAYMENTREQUEST_0_NAME18=Item18&L_PAYMENTREQUEST_0_NAME19=Item19&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE1=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE2=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE3=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE4=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE5=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE6=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE7=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE8=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE9=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE10=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE11=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE12=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE13=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE14=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE15=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE16=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE17=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE18=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE19=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT1=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT2=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT3=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT4=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT5=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT6=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT7=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT8=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT9=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT10=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT11=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT12=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT13=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT14=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT15=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT16=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT17=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT18=%20%20%200%2e00000&L_PAYMENTREQUEST_0_TAXAMT19=%20%20%200%2e00000&L_PAYMENTREQUEST_0_DESC0=A%20new%20Item%200&L_PAYMENTREQUEST_0_DESC1=A%20new%20Item%201&L_PAYMENTREQUEST_0_DESC2=A%20new%20Item%202&L_PAYMENTREQUEST_0_DESC3=A%20new%20Item%203&L_PAYMENTREQUEST_0_DESC4=A%20new%20Item%204&L_PAYMENTREQUEST_0_DESC5=A%20new%20Item%205&L_PAYMENTREQUEST_0_DESC6=A%20new%20Item%206&L_PAYMENTREQUEST_0_DESC7=A%20new%20Item%207&L_PAYMENTREQUEST_0_DESC8=A%20new%20Item%208&L_PAYMENTREQUEST_0_DESC9=A%20new%20Item%209&L_PAYMENTREQUEST_0_DESC10=A%20new%20Item%2010&L_PAYMENTREQUEST_0_DESC11=A%20new%20Item%2011&L_PAYMENTREQUEST_0_DESC12=A%20new%20Item%2012&L_PAYMENTREQUEST_0_DESC13=A%20new%20Item%2013&L_PAYMENTREQUEST_0_DESC14=A%20new%20Item%2014&L_PAYMENTREQUEST_0_DESC15=A%20new%20Item%2015&L_PAYMENTREQUEST_0_DESC16=A%20new%20Item%2016&L_PAYMENTREQUEST_0_DESC17=A%20new%20Item%2017&L_PAYMENTREQUEST_0_DESC18=A%20new%20Item%2018&L_PAYMENTREQUEST_0_DESC19=A%20new%20Item%2019&L_PAYMENTREQUEST_0_AMT0=50&L_PAYMENTREQUEST_0_AMT1=50&L_PAYMENTREQUEST_0_AMT2=50&L_PAYMENTREQUEST_0_AMT3=50&L_PAYMENTREQUEST_0_AMT4=50&L_PAYMENTREQUEST_0_AMT5=50&L_PAYMENTREQUEST_0_AMT6=50&L_PAYMENTREQUEST_0_AMT7=50&L_PAYMENTREQUEST_0_AMT8=50&L_PAYMENTREQUEST_0_AMT9=50&L_PAYMENTREQUEST_0_AMT10=50&L_PAYMENTREQUEST_0_AMT11=50&L_PAYMENTREQUEST_0_AMT12=50&L_PAYMENTREQUEST_0_AMT13=50&L_PAYMENTREQUEST_0_AMT14=50&L_PAYMENTREQUEST_0_AMT15=50&L_PAYMENTREQUEST_0_AMT16=50&L_PAYMENTREQUEST_0_AMT17=50&L_PAYMENTREQUEST_0_AMT18=50&L_PAYMENTREQUEST_0_AMT19=50&L_PAYMENTREQUEST_0_QTY0=1&L_PAYMENTREQUEST_0_QTY1=1&L_PAYMENTREQUEST_0_QTY2=1&L_PAYMENTREQUEST_0_QTY3=1&L_PAYMENTREQUEST_0_QTY4=1&L_PAYMENTREQUEST_0_QTY5=1&L_PAYMENTREQUEST_0_QTY6=1&L_PAYMENTREQUEST_0_QTY7=1&L_PAYMENTREQUEST_0_QTY8=1&L_PAYMENTREQUEST_0_QTY9=1&L_PAYMENTREQUEST_0_QTY10=1&L_PAYMENTREQUEST_0_QTY11=1&L_PAYMENTREQUEST_0_QTY12=1&L_PAYMENTREQUEST_0_QTY13=1&L_PAYMENTREQUEST_0_QTY14=1&L_PAYMENTREQUEST_0_QTY15=1&L_PAYMENTREQUEST_0_QTY16=1&L_PAYMENTREQUEST_0_QTY17=1&L_PAYMENTREQUEST_0_QTY18=1&L_PAYMENTREQUEST_0_QTY19=1&PAYMENTREQUESTINFO_0_ERRORCODE=0 ================================================ FILE: spec/fake_response/DoReferenceTransaction/failure.txt ================================================ TIMESTAMP=2011%2d07%2d19T08%3a59%3a52Z&CORRELATIONID=ed8096a3c3d7d&ACK=Failure&VERSION=72%2e0&BUILD=1982348&L_ERRORCODE0=11451&L_SHORTMESSAGE0=Billing%20Agreement%20Id%20or%20transaction%20Id%20is%20not%20valid&L_LONGMESSAGE0=Billing%20Agreement%20Id%20or%20transaction%20Id%20is%20not%20valid&L_SEVERITYCODE0=Error&TRANSACTIONTYPE=None&PAYMENTTYPE=None&ORDERTIME=1970%2d01%2d01T00%3a00%3a00Z&PAYMENTSTATUS=None&PENDINGREASON=None&REASONCODE=None ================================================ FILE: spec/fake_response/DoReferenceTransaction/success.txt ================================================ BILLINGAGREEMENTID=B%2d7HU5U5U1ULU14U49E&TIMESTAMP=2011%2d07%2d19T08%3a30%3a08Z&CORRELATIONID=12ca85fb3c9d2&ACK=Success&VERSION=72%2e0&BUILD=1982348&TRANSACTIONID=3IV51111LY1211907&TRANSACTIONTYPE=merchtpmt&PAYMENTTYPE=instant&ORDERTIME=2011%2d07%2d19T08%3a30%3a06Z&AMT=192&FEEAMT=47&TAXAMT=0&CURRENCYCODE=JPY&PAYMENTSTATUS=Completed&PENDINGREASON=None&REASONCODE=None&PROTECTIONELIGIBILITY=Ineligible&PROTECTIONELIGIBILITYTYPE=None ================================================ FILE: spec/fake_response/DoVoid/success.txt ================================================ ACK=Success&AUTHORIZATIONID=1a2b3c4d5e6f&BUILD=13055236&TIMESTAMP=2014-10-03T17%3A34%3A56Z ================================================ FILE: spec/fake_response/GetExpressCheckoutDetails/failure.txt ================================================ TOKEN=EC%2d9E2743126S4330617&CHECKOUTSTATUS=PaymentActionCompleted&TIMESTAMP=2011%2d02%2d08T07%3a10%3a26Z&CORRELATIONID=8f5fddf13ed10&ACK=Failure&VERSION=66%2e0&BUILD=1721431&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&EMAIL=valid_1296805890_per%40matake%2ejp&PAYERID=PRT3TZ6MCBCNC&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US&SHIPTONAME=Test%20User&SHIPTOSTREET=1%20Main%20St&SHIPTOCITY=San%20Jose&SHIPTOSTATE=CA&SHIPTOZIP=95131&SHIPTOCOUNTRYCODE=US&SHIPTOCOUNTRYNAME=United%20States&ADDRESSSTATUS=Confirmed&CURRENCYCODE=USD&AMT=14%2e00&SHIPPINGAMT=0%2e00&HANDLINGAMT=0%2e00&TAXAMT=0%2e00&INSURANCEAMT=0%2e00&SHIPDISCAMT=0%2e00&PAYMENTREQUEST_0_CURRENCYCODE=USD&PAYMENTREQUEST_0_AMT=14%2e00&PAYMENTREQUEST_0_SHIPPINGAMT=0%2e00&PAYMENTREQUEST_0_HANDLINGAMT=0%2e00&PAYMENTREQUEST_0_TAXAMT=0%2e00&PAYMENTREQUEST_0_INSURANCEAMT=0%2e00&PAYMENTREQUEST_0_SHIPDISCAMT=0%2e00&PAYMENTREQUEST_0_TRANSACTIONID=8NC65222871997739&PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED=false&PAYMENTREQUEST_0_SHIPTONAME=Test%20User&PAYMENTREQUEST_0_SHIPTOSTREET=1%20Main%20St&PAYMENTREQUEST_0_SHIPTOCITY=San%20Jose&PAYMENTREQUEST_0_SHIPTOSTATE=CA&PAYMENTREQUEST_0_SHIPTOZIP=95131&PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE=US&PAYMENTREQUEST_0_SHIPTOCOUNTRYNAME=United%20States&PAYMENTREQUESTINFO_0_TRANSACTIONID=8NC65222871997739&PAYMENTREQUESTINFO_0_ERRORCODE=0 ================================================ FILE: spec/fake_response/GetExpressCheckoutDetails/success.txt ================================================ TOKEN=EC%2d7HG122968U843864S&CHECKOUTSTATUS=PaymentActionNotInitiated&TIMESTAMP=2011%2d04%2d12T03%3a51%3a58Z&CORRELATIONID=6a0a92ea79ac3&ACK=Success&VERSION=69%2e0&BUILD=1824201&EMAIL=buyer_1297999636_per%40cerego%2ecom&PAYERID=9RWDTMRKKHQ8S&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US&CURRENCYCODE=JPY&AMT=10&ITEMAMT=10&SHIPPINGAMT=0&HANDLINGAMT=0&TAXAMT=0&DESC=Instant%20Payment%20Request&NOTIFYURL=http%3a%2f%2fmerchant%2eexample%2ecom%2fnotify&INSURANCEAMT=0&SHIPDISCAMT=0&L_NAME0=Item1&L_QTY0=1&L_TAXAMT0=0&L_AMT0=10&L_DESC0=Item1%20Desc&L_ITEMWEIGHTVALUE0=%20%20%200%2e00000&L_ITEMLENGTHVALUE0=%20%20%200%2e00000&L_ITEMWIDTHVALUE0=%20%20%200%2e00000&L_ITEMHEIGHTVALUE0=%20%20%200%2e00000&L_ITEMCATEGORY0=Digital&PAYMENTREQUEST_0_CURRENCYCODE=JPY&PAYMENTREQUEST_0_AMT=10&PAYMENTREQUEST_0_ITEMAMT=10&PAYMENTREQUEST_0_SHIPPINGAMT=0&PAYMENTREQUEST_0_HANDLINGAMT=0&PAYMENTREQUEST_0_TAXAMT=0&PAYMENTREQUEST_0_DESC=Instant%20Payment%20Request&PAYMENTREQUEST_0_NOTIFYURL=http%3a%2f%2fmerchant%2eexample%2ecom%2fnotify&PAYMENTREQUEST_0_INSURANCEAMT=0&PAYMENTREQUEST_0_SHIPDISCAMT=0&PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED=false&L_PAYMENTREQUEST_0_NAME0=Item1&L_PAYMENTREQUEST_0_QTY0=1&L_PAYMENTREQUEST_0_TAXAMT0=0&L_PAYMENTREQUEST_0_AMT0=10&L_PAYMENTREQUEST_0_DESC0=Item1%20Desc&L_PAYMENTREQUEST_0_ITEMWEIGHTVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMLENGTHVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMWIDTHVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMHEIGHTVALUE0=%20%20%200%2e00000&L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital&PAYMENTREQUESTINFO_0_ERRORCODE=0 ================================================ FILE: spec/fake_response/GetExpressCheckoutDetails/with_billing_accepted_status.txt ================================================ TOKEN=EC%2d4D299105F9409730D&BILLINGAGREEMENTACCEPTEDSTATUS=1&CHECKOUTSTATUS=PaymentActionNotInitiated&TIMESTAMP=2011%2d08%2d08T01%3a56%3a13Z&CORRELATIONID=53a48e61dfca7&ACK=Success&VERSION=78%2e0&BUILD=2020243&EMAIL=client_1305381804_per%40matake%2ejp&PAYERID=CMJLWADKVFQYJ&PAYERSTATUS=verified&FIRSTNAME=Test&LASTNAME=User&COUNTRYCODE=US&CURRENCYCODE=JPY&AMT=0&SHIPPINGAMT=0&HANDLINGAMT=0&TAXAMT=0&INSURANCEAMT=0&SHIPDISCAMT=0&PAYMENTREQUEST_0_CURRENCYCODE=JPY&PAYMENTREQUEST_0_AMT=0&PAYMENTREQUEST_0_SHIPPINGAMT=0&PAYMENTREQUEST_0_HANDLINGAMT=0&PAYMENTREQUEST_0_TAXAMT=0&PAYMENTREQUEST_0_INSURANCEAMT=0&PAYMENTREQUEST_0_SHIPDISCAMT=0&PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED=false&PAYMENTREQUESTINFO_0_ERRORCODE=0 ================================================ FILE: spec/fake_response/GetRecurringPaymentsProfileDetails/failure.txt ================================================ PROFILEID=I%2dK1VFLRN3VVG0x&TIMESTAMP=2011%2d02%2d08T07%3a19%3a43Z&CORRELATIONID=edc954f1c34c5&ACK=Failure&VERSION=66%2e0&BUILD=1704252&L_ERRORCODE0=11552&L_SHORTMESSAGE0=Invalid%20profile%20ID&L_LONGMESSAGE0=The%20profile%20ID%20is%20invalid&L_SEVERITYCODE0=Error ================================================ FILE: spec/fake_response/GetRecurringPaymentsProfileDetails/success.txt ================================================ PROFILEID=I%2dK1VFLRN3VVG0&STATUS=Active&AUTOBILLOUTAMT=NoAutoBill&DESC=iKnow%21%20Subscription&MAXFAILEDPAYMENTS=0&SUBSCRIBERNAME=Test%20User&PROFILESTARTDATE=2011%2d02%2d03T15%3a00%3a00Z&NEXTBILLINGDATE=2011%2d03%2d04T10%3a00%3a00Z&NUMCYCLESCOMPLETED=1&NUMCYCLESREMAINING=18446744073709551615&OUTSTANDINGBALANCE=0&FAILEDPAYMENTCOUNT=0&LASTPAYMENTDATE=2011%2d02%2d04T10%3a50%3a56Z&LASTPAYMENTAMT=1000&TRIALAMTPAID=0®ULARAMTPAID=1000&AGGREGATEAMT=1000&AGGREGATEOPTIONALAMT=0&FINALPAYMENTDUEDATE=1970%2d01%2d01T00%3a00%3a00Z&TIMESTAMP=2011%2d02%2d08T07%3a19%3a25Z&CORRELATIONID=8e18dd6545045&ACK=Success&VERSION=66%2e0&BUILD=1704252&SHIPTOSTREET=1%20Main%20St&SHIPTOCITY=San%20Jose&SHIPTOSTATE=CA&SHIPTOZIP=95131&SHIPTOCOUNTRYCODE=US&SHIPTOCOUNTRY=US&SHIPTOCOUNTRYNAME=United%20States&SHIPADDRESSOWNER=PayPal&SHIPADDRESSSTATUS=Unconfirmed&BILLINGPERIOD=Month&BILLINGFREQUENCY=1&TOTALBILLINGCYCLES=0&CURRENCYCODE=JPY&AMT=1000&SHIPPINGAMT=0&TAXAMT=0®ULARBILLINGPERIOD=Month®ULARBILLINGFREQUENCY=1®ULARTOTALBILLINGCYCLES=0®ULARCURRENCYCODE=JPY®ULARAMT=1000®ULARSHIPPINGAMT=0®ULARTAXAMT=0 ================================================ FILE: spec/fake_response/GetTransactionDetails/failure.txt ================================================ ADDRESSOWNER=PayPal&ADDRESSSTATUS=None&TIMESTAMP=2012%2d02%2d23T21%3a07%3a22Z&CORRELATIONID=4e18d75a23953&ACK=Failure&VERSION=78%2e0&BUILD=2571254&L_ERRORCODE0=10004&L_SHORTMESSAGE0=Transaction%20refused%20because%20of%20an%20invalid%20argument%2e%20See%20additional%20error%20messages%20for%20details%2e&L_LONGMESSAGE0=The%20transaction%20id%20is%20not%20valid&L_SEVERITYCODE0=Error&PENDINGREASON=None&REASONCODE=None ================================================ FILE: spec/fake_response/GetTransactionDetails/success.txt ================================================ RECEIVEREMAIL=test%40email%2ecom&RECEIVERID=ABCDEFG12345&EMAIL=test%40email%2ecom&PAYERID=12345ABCDEFG&PAYERSTATUS=verified&COUNTRYCODE=US&SHIPTONAME=Billy%20Bob&SHIPTOSTREET=1%20Main%20St&SHIPTOCITY=San%20Jose&SHIPTOSTATE=CA&SHIPTOCOUNTRYCODE=US&SHIPTOCOUNTRYNAME=United%20States&SHIPTOZIP=95131&ADDRESSOWNER=PayPal&ADDRESSSTATUS=Confirmed&SALESTAX=0%2e00&SUBJECT=Test%20Subject&TIMESTAMP=2012%2d02%2d23T20%3a51%3a29Z&CORRELATIONID=4fc0a06cbaec7&ACK=Success&VERSION=78%2e0&BUILD=2571254&FIRSTNAME=Billy&LASTNAME=Bob&TRANSACTIONID=123456789ABCD&TRANSACTIONTYPE=cart&PAYMENTTYPE=instant&ORDERTIME=2012%2d02%2d13T23%3a09%3a46Z&AMT=740%2e43&FEEAMT=21%2e77&TAXAMT=0%2e00&SHIPPINGAMT=0%2e00&HANDLINGAMT=0%2e00&CURRENCYCODE=USD&PAYMENTSTATUS=Completed&PENDINGREASON=None&REASONCODE=None&PROTECTIONELIGIBILITY=Eligible&PROTECTIONELIGIBILITYTYPE=ItemNotReceivedEligible%2cUnauthorizedPaymentEligible&L_NAME0=Item%201&L_NAME1=Item%202&L_QTY0=1&L_QTY1=1&L_SHIPPINGAMT0=0%2e00&L_SHIPPINGAMT1=0%2e00&L_HANDLINGAMT0=0%2e00&L_HANDLINGAMT1=0%2e00&L_CURRENCYCODE0=USD&L_CURRENCYCODE1=USD&L_AMT0=687%2e93&L_AMT1=52%2e50 ================================================ FILE: spec/fake_response/IPN/invalid.txt ================================================ INVALID ================================================ FILE: spec/fake_response/IPN/valid.txt ================================================ VERIFIED ================================================ FILE: spec/fake_response/ManageRecurringPaymentsProfileStatus/failure.txt ================================================ TIMESTAMP=2011%2d02%2d08T07%3a21%3a08Z&CORRELATIONID=aa160cad77481&ACK=Failure&VERSION=66%2e0&BUILD=1704252&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 ================================================ FILE: spec/fake_response/ManageRecurringPaymentsProfileStatus/success.txt ================================================ PROFILEID=I%2dK1VFLRN3VVG0&TIMESTAMP=2011%2d02%2d08T07%3a20%3a44Z&CORRELATIONID=86bde487ccba6&ACK=Success&VERSION=66%2e0&BUILD=1704252 ================================================ FILE: spec/fake_response/RefundTransaction/full.txt ================================================ REFUNDTRANSACTIONID=6D456341FS516215S&FEEREFUNDAMT=0%2e50&GROSSREFUNDAMT=10%2e00&NETREFUNDAMT=9%2e50&CURRENCYCODE=USD&TOTALREFUNDEDAMOUNT=10%2e00&TIMESTAMP=2011%2d05%2d21T14%3a13%3a32Z&CORRELATIONID=862bfc523cca2&ACK=Success&VERSION=72%2e0&BUILD=1882144 ================================================ FILE: spec/fake_response/SetExpressCheckout/failure.txt ================================================ TIMESTAMP=2011%2d02%2d02T02%3a16%3a50Z&CORRELATIONID=379d1b7f97afb&ACK=Failure&L_ERRORCODE0=10001&L_SHORTMESSAGE0=Internal%20Error&L_LONGMESSAGE0=Timeout%20processing%20request ================================================ FILE: spec/fake_response/SetExpressCheckout/success.txt ================================================ ACK=Success&BUILD=1721431&CORRELATIONID=5549ea3a78af1&TIMESTAMP=2011-02-02T02%3A02%3A18Z&TOKEN=EC-5YJ90598G69065317&VERSION=66.0 ================================================ FILE: spec/helpers/fake_response_helper.rb ================================================ require 'fakeweb' module FakeResponseHelper def fake_response(file_path, api = :NVP, options = {}) endpoint = case api when :NVP Paypal::NVP::Request.endpoint when :IPN Paypal::IPN.endpoint else raise "Non-supported API: #{api}" end FakeWeb.register_uri( :post, endpoint, options.merge( :body => File.read(File.join(File.dirname(__FILE__), '../fake_response', "#{file_path}.txt")) ) ) end def request_to(endpoint, method = :get) raise_error( FakeWeb::NetConnectNotAllowedError, "Real HTTP connections are disabled. Unregistered request: #{method.to_s.upcase} #{endpoint}" ) end end FakeWeb.allow_net_connect = false include FakeResponseHelper ================================================ FILE: spec/paypal/exception/api_error_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Exception::APIError do let(:error) { Paypal::Exception::APIError.new(params) } context 'when Hash is given' do let :params do { :VERSION=>"66.0", :TIMESTAMP=>"2011-03-03T06:33:51Z", :CORRELATIONID=>"758ebdc546b9c", :BUILD=>"1741654", :ACK=>"Failure", :L_SEVERITYCODE0=>"Error", :L_ERRORCODE0=>"10411", :L_LONGMESSAGE0=>"This Express Checkout session has expired. Token value is no longer valid.", :L_SHORTMESSAGE0=>"This Express Checkout session has expired.", :L_SEVERITYCODE1=>"Error", :L_ERRORCODE1=>"2468", :L_LONGMESSAGE1=>"Sample of a long message for the second item.", :L_SHORTMESSAGE1=>"Second short message.", } end describe "#message" do it "aggregates short messages" do error.message.should == "PayPal API Error: 'This Express Checkout session has expired.', 'Second short message.'" end end describe '#subject' do subject { error.response } its(:raw) { should == params } Paypal::Exception::APIError::Response.attribute_mapping.each do |key, attribute| its(attribute) { should == params[key] } end describe '#details' do subject { error.response.details.first } Paypal::Exception::APIError::Response::Detail.attribute_mapping.each do |key, attribute| its(attribute) { should == params[:"L_#{key}0"] } end end end end context 'when unknown params given' do let :params do { :UNKNOWN => 'Unknown', :L_UNKNOWN0 => 'Unknown Detail' } end it 'should warn' do Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Exception::APIError::Response): UNKNOWN=Unknown" ) Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Exception::APIError::Response::Detail): UNKNOWN=Unknown Detail" ) error end describe '#response' do subject { error.response } its(:raw) { should == params } end its(:message) { should == "PayPal API Error" } end context 'otherwise' do subject { error } let(:params) { 'Failure' } its(:response) { should == params } its(:message) { should == "PayPal API Error" } end end ================================================ FILE: spec/paypal/exception/http_error_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Exception::HttpError do subject { Paypal::Exception::HttpError.new(400, 'BadRequest', 'You are bad man!') } its(:code) { should == 400 } its(:message) { should == 'BadRequest' } its(:body) { should == 'You are bad man!' } end ================================================ FILE: spec/paypal/express/request_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Express::Request do class Paypal::Express::Request attr_accessor :_sent_params_, :_method_ def post_with_logging(method, params) self._method_ = method self._sent_params_ = params post_without_logging method, params end alias_method_chain :post, :logging end let(:return_url) { 'http://example.com/success' } let(:cancel_url) { 'http://example.com/cancel' } let(:nvp_endpoint) { Paypal::NVP::Request::ENDPOINT[:production] } let :attributes do { :username => 'nov', :password => 'password', :signature => 'sig' } end let :instance do Paypal::Express::Request.new attributes end let :instant_payment_request do Paypal::Payment::Request.new( :amount => 1000, :description => 'Instant Payment Request' ) end let :many_items do items = Array.new (1..20).each do |index| items << Paypal::Payment::Request::Item.new( :name => "Item#{index.to_s}", :description => "A new Item #{index.to_s}", :amount => 50.00, :quantity => 1 ) end end let :instant_payment_request_with_many_items do Paypal::Payment::Request.new( :amount => 1000, :description => 'Instant Payment Request', :items => many_items ) end let :recurring_payment_request do Paypal::Payment::Request.new( :billing_type => :RecurringPayments, :billing_agreement_description => 'Recurring Payment Request' ) end let :recurring_profile do Paypal::Payment::Recurring.new( :start_date => Time.utc(2011, 2, 8, 9, 0, 0), :description => 'Recurring Profile', :billing => { :period => :Month, :frequency => 1, :amount => 1000 } ) end let :reference_transaction_request do Paypal::Payment::Request.new( :billing_type => :MerchantInitiatedBilling, :billing_agreement_description => 'Billing Agreement Request' ) end describe '.new' do context 'when any required parameters are missing' do it 'should raise AttrRequired::AttrMissing' do attributes.keys.each do |missing_key| insufficient_attributes = attributes.reject do |key, value| key == missing_key end expect do Paypal::Express::Request.new insufficient_attributes end.to raise_error AttrRequired::AttrMissing end end end context 'when all required parameters are given' do it 'should succeed' do expect do Paypal::Express::Request.new attributes end.not_to raise_error AttrRequired::AttrMissing end end end describe '#setup' do it 'should return Paypal::Express::Response' do fake_response 'SetExpressCheckout/success' response = instance.setup recurring_payment_request, return_url, cancel_url response.should be_instance_of Paypal::Express::Response end it 'should support no_shipping option' do expect do instance.setup instant_payment_request, return_url, cancel_url, :no_shipping => true end.to request_to nvp_endpoint, :post instance._method_.should == :SetExpressCheckout instance._sent_params_.should == { :PAYMENTREQUEST_0_DESC => 'Instant Payment Request', :RETURNURL => return_url, :CANCELURL => cancel_url, :PAYMENTREQUEST_0_AMT => '1000.00', :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00", :REQCONFIRMSHIPPING => 0, :NOSHIPPING => 1 } end it 'should support allow_note=false option' do expect do instance.setup instant_payment_request, return_url, cancel_url, :allow_note => false end.to request_to nvp_endpoint, :post instance._method_.should == :SetExpressCheckout instance._sent_params_.should == { :PAYMENTREQUEST_0_DESC => 'Instant Payment Request', :RETURNURL => return_url, :CANCELURL => cancel_url, :PAYMENTREQUEST_0_AMT => '1000.00', :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00", :ALLOWNOTE => 0 } end { :solution_type => :SOLUTIONTYPE, :landing_page => :LANDINGPAGE, :email => :EMAIL, :brand => :BRANDNAME, :locale => :LOCALECODE, :logo => :LOGOIMG, :cart_border_color => :CARTBORDERCOLOR, :payflow_color => :PAYFLOWCOLOR }.each do |option_key, param_key| it "should support #{option_key} option" do expect do instance.setup instant_payment_request, return_url, cancel_url, option_key => 'some value' end.to request_to nvp_endpoint, :post instance._method_.should == :SetExpressCheckout instance._sent_params_.should include param_key instance._sent_params_[param_key].should == 'some value' end end context 'when instance payment request given' do it 'should call SetExpressCheckout' do expect do instance.setup instant_payment_request, return_url, cancel_url end.to request_to nvp_endpoint, :post instance._method_.should == :SetExpressCheckout instance._sent_params_.should == { :PAYMENTREQUEST_0_DESC => 'Instant Payment Request', :RETURNURL => return_url, :CANCELURL => cancel_url, :PAYMENTREQUEST_0_AMT => '1000.00', :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00" } end end context 'when recurring payment request given' do it 'should call SetExpressCheckout' do expect do instance.setup recurring_payment_request, return_url, cancel_url end.to request_to nvp_endpoint, :post instance._method_.should == :SetExpressCheckout instance._sent_params_.should == { :L_BILLINGTYPE0 => :RecurringPayments, :L_BILLINGAGREEMENTDESCRIPTION0 => 'Recurring Payment Request', :RETURNURL => return_url, :CANCELURL => cancel_url, :PAYMENTREQUEST_0_AMT => '0.00', :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00" } end end context 'when reference transaction request given' do it 'should call SetExpressCheckout' do expect do instance.setup reference_transaction_request, return_url, cancel_url end.to request_to nvp_endpoint, :post instance._method_.should == :SetExpressCheckout instance._sent_params_.should == { :L_BILLINGTYPE0 => :MerchantInitiatedBilling, :L_BILLINGAGREEMENTDESCRIPTION0 => 'Billing Agreement Request', :RETURNURL => return_url, :CANCELURL => cancel_url, :PAYMENTREQUEST_0_AMT => '0.00', :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00" } end end end describe '#details' do it 'should return Paypal::Express::Response' do fake_response 'GetExpressCheckoutDetails/success' response = instance.details 'token' response.should be_instance_of Paypal::Express::Response end it 'should call GetExpressCheckoutDetails' do expect do instance.details 'token' end.to request_to nvp_endpoint, :post instance._method_.should == :GetExpressCheckoutDetails instance._sent_params_.should == { :TOKEN => 'token' } end end describe '#transaction_details' do it 'should return Paypal::Express::Response' do fake_response 'GetTransactionDetails/success' response = instance.transaction_details 'transaction_id' response.should be_instance_of Paypal::Express::Response end it 'should call GetTransactionDetails' do expect do instance.transaction_details 'transaction_id' end.to request_to nvp_endpoint, :post instance._method_.should == :GetTransactionDetails instance._sent_params_.should == { :TRANSACTIONID=> 'transaction_id' } end it 'should fail with bad transaction id' do expect do fake_response 'GetTransactionDetails/failure' response = instance.transaction_details 'bad_transaction_id' end.to raise_error(Paypal::Exception::APIError) end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) fake_response 'GetTransactionDetails/success' response = instance.transaction_details 'transaction_id' end end describe "#capture!" do it 'should return Paypal::Express::Response' do fake_response 'DoCapture/success' response = instance.capture! 'authorization_id', 181.98, :BRL response.should be_instance_of Paypal::Express::Response end it 'should call DoExpressCheckoutPayment' do expect do instance.capture! 'authorization_id', 181.98, :BRL end.to request_to nvp_endpoint, :post instance._method_.should == :DoCapture instance._sent_params_.should == { :AUTHORIZATIONID => 'authorization_id', :COMPLETETYPE => 'Complete', :AMT => 181.98, :CURRENCYCODE => :BRL } end it 'should call DoExpressCheckoutPayment with NotComplete capture parameter' do expect do instance.capture! 'authorization_id', 181.98, :BRL, 'NotComplete' end.to request_to nvp_endpoint, :post instance._method_.should == :DoCapture instance._sent_params_.should == { :AUTHORIZATIONID => 'authorization_id', :COMPLETETYPE => 'NotComplete', :AMT => 181.98, :CURRENCYCODE => :BRL } end end describe "#void!" do it 'should return Paypal::Express::Response' do fake_response 'DoVoid/success' response = instance.void! 'authorization_id', note: "note" response.should be_instance_of Paypal::Express::Response end it 'should call DoVoid' do expect do instance.void! 'authorization_id', note: "note" end.to request_to nvp_endpoint, :post instance._method_.should == :DoVoid instance._sent_params_.should == { :AUTHORIZATIONID => 'authorization_id', :NOTE => "note" } end end describe '#checkout!' do it 'should return Paypal::Express::Response' do fake_response 'DoExpressCheckoutPayment/success' response = instance.checkout! 'token', 'payer_id', instant_payment_request response.should be_instance_of Paypal::Express::Response end it 'should call DoExpressCheckoutPayment' do expect do instance.checkout! 'token', 'payer_id', instant_payment_request end.to request_to nvp_endpoint, :post instance._method_.should == :DoExpressCheckoutPayment instance._sent_params_.should == { :PAYERID => 'payer_id', :TOKEN => 'token', :PAYMENTREQUEST_0_DESC => 'Instant Payment Request', :PAYMENTREQUEST_0_AMT => '1000.00', :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00" } end context "with many items" do before do fake_response 'DoExpressCheckoutPayment/success_with_many_items' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = instance.checkout! 'token', 'payer_id', instant_payment_request_with_many_items end it 'should return Paypal::Express::Response' do response = instance.checkout! 'token', 'payer_id', instant_payment_request_with_many_items response.should be_instance_of Paypal::Express::Response end it 'should return twenty items' do response = instance.checkout! 'token', 'payer_id', instant_payment_request_with_many_items instance._method_.should == :DoExpressCheckoutPayment response.items.count.should == 20 end end end describe '#subscribe!' do it 'should return Paypal::Express::Response' do fake_response 'CreateRecurringPaymentsProfile/success' response = instance.subscribe! 'token', recurring_profile response.should be_instance_of Paypal::Express::Response end it 'should call CreateRecurringPaymentsProfile' do expect do instance.subscribe! 'token', recurring_profile end.to request_to nvp_endpoint, :post instance._method_.should == :CreateRecurringPaymentsProfile instance._sent_params_.should == { :DESC => 'Recurring Profile', :TOKEN => 'token', :SHIPPINGAMT => '0.00', :AMT => '1000.00', :BILLINGFREQUENCY => 1, :MAXFAILEDPAYMENTS => 0, :BILLINGPERIOD => :Month, :TAXAMT => '0.00', :PROFILESTARTDATE => '2011-02-08 09:00:00', :TOTALBILLINGCYCLES => 0 } end end describe '#subscription' do it 'should return Paypal::Express::Response' do fake_response 'GetRecurringPaymentsProfileDetails/success' response = instance.subscription 'profile_id' response.should be_instance_of(Paypal::Express::Response) end it 'should call GetRecurringPaymentsProfileDetails' do expect do instance.subscription 'profile_id' end.to request_to nvp_endpoint, :post instance._method_.should == :GetRecurringPaymentsProfileDetails instance._sent_params_.should == { :PROFILEID => 'profile_id' } end end describe '#renew!' do it 'should return Paypal::Express::Response' do fake_response 'ManageRecurringPaymentsProfileStatus/success' response = instance.renew! 'profile_id', :Cancel response.should be_instance_of Paypal::Express::Response end it 'should call ManageRecurringPaymentsProfileStatus' do expect do instance.renew! 'profile_id', :Cancel end.to request_to nvp_endpoint, :post instance._method_.should == :ManageRecurringPaymentsProfileStatus instance._sent_params_.should == { :ACTION => :Cancel, :PROFILEID => 'profile_id' } end end describe '#cancel!' do it 'should return Paypal::Express::Response' do fake_response 'ManageRecurringPaymentsProfileStatus/success' response = instance.cancel! 'profile_id' response.should be_instance_of(Paypal::Express::Response) end it 'should call ManageRecurringPaymentsProfileStatus' do expect do instance.cancel! 'profile_id' end.to request_to nvp_endpoint, :post instance._method_.should == :ManageRecurringPaymentsProfileStatus instance._sent_params_.should == { :ACTION => :Cancel, :PROFILEID => 'profile_id' } end end describe '#suspend!' do it 'should return Paypal::Express::Response' do fake_response 'ManageRecurringPaymentsProfileStatus/success' response = instance.cancel! 'profile_id' response.should be_instance_of Paypal::Express::Response end it 'should call ManageRecurringPaymentsProfileStatus' do expect do instance.suspend! 'profile_id' end.to request_to nvp_endpoint, :post instance._method_.should == :ManageRecurringPaymentsProfileStatus instance._sent_params_.should == { :ACTION => :Suspend, :PROFILEID => 'profile_id' } end end describe '#reactivate!' do it 'should return Paypal::Express::Response' do fake_response 'ManageRecurringPaymentsProfileStatus/success' response = instance.cancel! 'profile_id' response.should be_instance_of Paypal::Express::Response end it 'should call ManageRecurringPaymentsProfileStatus' do expect do instance.reactivate! 'profile_id' end.to request_to nvp_endpoint, :post instance._method_.should == :ManageRecurringPaymentsProfileStatus instance._sent_params_.should == { :ACTION => :Reactivate, :PROFILEID => 'profile_id' } end end describe '#agree!' do it 'should return Paypal::Express::Response' do fake_response 'CreateBillingAgreement/success' response = instance.agree! 'token' response.should be_instance_of Paypal::Express::Response end it 'should call CreateBillingAgreement' do expect do instance.agree! 'token' end.to request_to nvp_endpoint, :post instance._method_.should == :CreateBillingAgreement instance._sent_params_.should == { :TOKEN => 'token' } end end describe '#agreement' do it 'should return Paypal::Express::Response' do fake_response 'BillAgreementUpdate/fetch' response = instance.agreement 'reference_id' response.should be_instance_of Paypal::Express::Response end it 'should call BillAgreementUpdate' do expect do instance.agreement 'reference_id' end.to request_to nvp_endpoint, :post instance._method_.should == :BillAgreementUpdate instance._sent_params_.should == { :REFERENCEID => 'reference_id' } end end describe '#charge!' do it 'should return Paypal::Express::Response' do fake_response 'DoReferenceTransaction/success' response = instance.charge! 'billing_agreement_id', 1000 response.should be_instance_of Paypal::Express::Response end it 'should call DoReferenceTransaction' do expect do instance.charge! 'billing_agreement_id', 1000, :currency_code => :JPY end.to request_to nvp_endpoint, :post instance._method_.should == :DoReferenceTransaction instance._sent_params_.should == { :REFERENCEID => 'billing_agreement_id', :AMT => '1000.00', :PAYMENTACTION => :Sale, :CURRENCYCODE => :JPY } end end describe '#revoke!' do it 'should return Paypal::Express::Response' do fake_response 'BillAgreementUpdate/revoke' response = instance.revoke! 'reference_id' response.should be_instance_of Paypal::Express::Response end it 'should call BillAgreementUpdate' do expect do instance.revoke! 'reference_id' end.to request_to nvp_endpoint, :post instance._method_.should == :BillAgreementUpdate instance._sent_params_.should == { :REFERENCEID => 'reference_id', :BillingAgreementStatus => :Canceled } end end describe '#refund!' do it 'should return Paypal::Express::Response' do fake_response 'RefundTransaction/full' response = instance.refund! 'transaction_id' response.should be_instance_of Paypal::Express::Response end it 'should call RefundTransaction' do expect do instance.refund! 'transaction_id' end.to request_to nvp_endpoint, :post instance._method_.should == :RefundTransaction instance._sent_params_.should == { :TRANSACTIONID => 'transaction_id', :REFUNDTYPE => :Full } end end end ================================================ FILE: spec/paypal/express/response_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Express::Response do before { fake_response 'SetExpressCheckout/success' } let(:return_url) { 'http://example.com/success' } let(:cancel_url) { 'http://example.com/cancel' } let :request do Paypal::Express::Request.new( :username => 'nov', :password => 'password', :signature => 'sig' ) end let :payment_request do Paypal::Payment::Request.new( :billing_type => :RecurringPayments, :billing_agreement_description => 'Recurring Payment Request' ) end let(:response) { request.setup payment_request, return_url, cancel_url } describe '#redirect_uri' do subject { response.redirect_uri } it { should include 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=' } end describe '#popup_uri' do subject { response.popup_uri } it { should include 'https://www.paypal.com/incontext?token=' } end context 'when pay_on_paypal option is given' do let(:response) { request.setup payment_request, return_url, cancel_url, :pay_on_paypal => true } subject { response } its(:pay_on_paypal) { should be_true } its(:query) { should include(:useraction => 'commit') } describe '#redirect_uri' do subject { response.redirect_uri } it { should include 'useraction=commit' } end describe '#popup_uri' do subject { response.popup_uri } it { should include 'useraction=commit' } end end context 'when sandbox mode' do before do Paypal.sandbox! fake_response 'SetExpressCheckout/success' end after { Paypal.sandbox = false } describe '#redirect_uri' do subject { response.redirect_uri } it { should include 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=' } end describe '#popup_uri' do subject { response.popup_uri } it { should include 'https://www.sandbox.paypal.com/incontext?token=' } end end context 'when mobile option is given' do let(:response) { request.setup payment_request, return_url, cancel_url, :mobile => true } subject { response } describe '#redirect_uri' do subject { response.redirect_uri } it { should include 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout-mobile&token=' } end end end ================================================ FILE: spec/paypal/ipn_spec.rb ================================================ require 'spec_helper' describe Paypal::IPN do describe '.verify!' do context 'when valid' do before { fake_response 'IPN/valid', :IPN } subject { Paypal::IPN.verify!('raw-post-body') } it { should be_true } end context 'when invalid' do before { fake_response 'IPN/invalid', :IPN } subject {} it do expect { Paypal::IPN.verify!('raw-post-body') }.to raise_error(Paypal::Exception::APIError) end end end end ================================================ FILE: spec/paypal/nvp/request_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::NVP::Request do let :attributes do { :username => 'nov', :password => 'password', :signature => 'sig' } end let :instance do Paypal::NVP::Request.new attributes end describe '.new' do context 'when any required parameters are missing' do it 'should raise AttrRequired::AttrMissing' do attributes.keys.each do |missing_key| insufficient_attributes = attributes.reject do |key, value| key == missing_key end expect do Paypal::NVP::Request.new insufficient_attributes end.to raise_error AttrRequired::AttrMissing end end end context 'when all required parameters are given' do it 'should succeed' do expect do Paypal::NVP::Request.new attributes end.not_to raise_error AttrRequired::AttrMissing end it 'should setup endpoint and version' do client = Paypal::NVP::Request.new attributes client.class.endpoint.should == Paypal::NVP::Request::ENDPOINT[:production] end it 'should support sandbox mode' do sandbox_mode do client = Paypal::NVP::Request.new attributes client.class.endpoint.should == Paypal::NVP::Request::ENDPOINT[:sandbox] end end end context 'when optional parameters are given' do let(:optional_attributes) do { :subject => 'user@example.com' } end it 'should setup subject' do client = Paypal::NVP::Request.new attributes.merge(optional_attributes) client.subject.should == 'user@example.com' end end end describe '#common_params' do { :username => :USER, :password => :PWD, :signature => :SIGNATURE, :subject => :SUBJECT, :version => :VERSION }.each do |option_key, param_key| it "should include :#{param_key}" do instance.common_params.should include(param_key) end it "should set :#{param_key} as #{option_key}" do instance.common_params[param_key].should == instance.send(option_key) end end end describe '#request' do it 'should POST to NPV endpoint' do expect do instance.request :RPCMethod end.to request_to Paypal::NVP::Request::ENDPOINT[:production], :post end context 'when got API error response' do before do fake_response 'SetExpressCheckout/failure' end it 'should raise Paypal::Exception::APIError' do expect do instance.request :SetExpressCheckout end.to raise_error(Paypal::Exception::APIError) end end context 'when got HTTP error response' do before do FakeWeb.register_uri( :post, Paypal::NVP::Request::ENDPOINT[:production], :body => "Invalid Request", :status => ["400", "Bad Request"] ) end it 'should raise Paypal::Exception::HttpError' do expect do instance.request :SetExpressCheckout end.to raise_error(Paypal::Exception::HttpError) end end end end ================================================ FILE: spec/paypal/nvp/response_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::NVP::Response do let(:return_url) { 'http://example.com/success' } let(:cancel_url) { 'http://example.com/cancel' } let :request do Paypal::Express::Request.new( :username => 'nov', :password => 'password', :signature => 'sig' ) end let :payment_request do Paypal::Payment::Request.new( :amount => 1000, :description => 'Instant Payment Request' ) end let :recurring_profile do Paypal::Payment::Recurring.new( :start_date => Time.utc(2011, 2, 8, 9, 0, 0), :description => 'Recurring Profile', :billing => { :period => :Month, :frequency => 1, :amount => 1000 } ) end describe '.new' do context 'when non-supported attributes are given' do it 'should ignore them and warn' do Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::NVP::Response): ignored=Ignore me!" ) Paypal::NVP::Response.new( :ignored => 'Ignore me!' ) end end context 'when SetExpressCheckout response given' do before do fake_response 'SetExpressCheckout/success' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = request.setup payment_request, return_url, cancel_url response.token.should == 'EC-5YJ90598G69065317' end end context 'when GetExpressCheckoutDetails response given' do before do fake_response 'GetExpressCheckoutDetails/success' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = request.details 'token' response.payer.identifier.should == '9RWDTMRKKHQ8S' response.payment_responses.size.should == 1 response.payment_info.size.should == 0 response.payment_responses.first.should be_instance_of(Paypal::Payment::Response) end context 'when BILLINGAGREEMENTACCEPTEDSTATUS included' do before do fake_response 'GetExpressCheckoutDetails/with_billing_accepted_status' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = request.details 'token' end end end context 'when DoExpressCheckoutPayment response given' do before do fake_response 'DoExpressCheckoutPayment/success' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = request.checkout! 'token', 'payer_id', payment_request response.payment_responses.size.should == 0 response.payment_info.size.should == 1 response.payment_info.first.should be_instance_of(Paypal::Payment::Response::Info) end context 'when billing_agreement is included' do before do fake_response 'DoExpressCheckoutPayment/success_with_billing_agreement' end it 'should have billing_agreement' do Paypal.logger.should_not_receive(:warn) response = request.checkout! 'token', 'payer_id', payment_request response.billing_agreement.identifier.should == 'B-1XR87946TC504770W' end end end context 'when CreateRecurringPaymentsProfile response given' do before do fake_response 'CreateRecurringPaymentsProfile/success' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = request.subscribe! 'token', recurring_profile response.recurring.identifier.should == 'I-L8N58XFUCET3' end end context 'when GetRecurringPaymentsProfileDetails response given' do before do fake_response 'GetRecurringPaymentsProfileDetails/success' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) response = request.subscription 'profile_id' response.recurring.billing.amount.total.should == 1000 response.recurring.regular_billing.paid.should == 1000 response.recurring.summary.next_billing_date.should == '2011-03-04T10:00:00Z' end end context 'when ManageRecurringPaymentsProfileStatus response given' do before do fake_response 'ManageRecurringPaymentsProfileStatus/success' end it 'should handle all attributes' do Paypal.logger.should_not_receive(:warn) request.renew! 'profile_id', :Cancel end end end end ================================================ FILE: spec/paypal/payment/common/amount_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Common::Amount do let :keys do Paypal::Payment::Common::Amount.optional_attributes end describe '.new' do it 'should not allow nil for attributes' do amount = Paypal::Payment::Common::Amount.new keys.each do |key| amount.send(key).should == 0 end end it 'should treat all attributes as Numeric' do # Integer attributes = keys.inject({}) do |attributes, key| attributes.merge!(key => "100") end amount = Paypal::Payment::Common::Amount.new attributes keys.each do |key| amount.send(key).should == 100 end # Float attributes = keys.inject({}) do |attributes, key| attributes.merge!(key => "10.25") end amount = Paypal::Payment::Common::Amount.new attributes keys.each do |key| amount.send(key).should == 10.25 end end end end ================================================ FILE: spec/paypal/payment/recurring/activation_spec.rb ================================================ require 'spec_helper' describe Paypal::Payment::Recurring::Activation do let :instance do Paypal::Payment::Recurring::Activation.new( :initial_amount => 100, :failed_action => 'ContinueOnFailure' ) end describe '#to_params' do it 'should handle Recurring Profile activation parameters' do instance.to_params.should == { :INITAMT => '100.00', :FAILEDINITAMTACTION => 'ContinueOnFailure' } end end end ================================================ FILE: spec/paypal/payment/recurring_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Recurring do let :keys do Paypal::Payment::Recurring.optional_attributes end let :trial_attributes do {} end let :attributes do { :identifier => '12345', :description => 'Subscription Payment Profile', :status => 'Active', :start_date => '2011-02-03T15:00:00Z', :name => 'Nov Matake', :auto_bill => 'NoAutoBill', :max_fails => '0', :aggregate_amount => '1000', :aggregate_optional_amount => '0', :final_payment_date => '1970-01-01T00:00:00Z', :billing => { :amount => Paypal::Payment::Common::Amount.new( :total => '1000', :shipping => '0', :tax => '0' ), :currency_code => 'JPY', :period => 'Month', :frequency => '1', :total_cycles => '0', :trial => trial_attributes }, :regular_billing => { :amount => '1000', :shipping_amount => '0', :tax_amount => '0', :currency_code => 'JPY', :period => 'Month', :frequency => '1', :total_cycles => '0', :paid => '1000' }, :summary => { :next_billing_date => '2011-03-04T10:00:00Z', :cycles_completed => '1', :cycles_remaining => '18446744073709551615', :outstanding_balance => '0', :failed_count => '0', :last_payment_date => '2011-02-04T10:50:56Z', :last_payment_amount => '1000' } } end let :instance do Paypal::Payment::Recurring.new attributes end describe '.new' do it 'should accept all supported attributes' do instance.identifier.should == '12345' instance.description.should == 'Subscription Payment Profile' instance.status.should == 'Active' instance.start_date.should == '2011-02-03T15:00:00Z' instance.billing.trial.should be_nil end context 'when optional trial info given' do let :trial_attributes do { :period => 'Month', :frequency => '1', :total_cycles => '0', :currency_code => 'JPY', :amount => '1000', :shipping_amount => '0', :tax_amount => '0' } end it 'should setup trial billing info' do instance.billing.trial.should == Paypal::Payment::Recurring::Billing.new(trial_attributes) end end end describe '#to_params' do it 'should handle Recurring Profile parameters' do instance.to_params.should == { :AUTOBILLOUTAMT => 'NoAutoBill', :BILLINGFREQUENCY => 1, :SHIPPINGAMT => '0.00', :DESC => 'Subscription Payment Profile', :SUBSCRIBERNAME => 'Nov Matake', :BILLINGPERIOD => 'Month', :AMT => '1000.00', :MAXFAILEDPAYMENTS => 0, :TOTALBILLINGCYCLES => 0, :TAXAMT => '0.00', :PROFILESTARTDATE => '2011-02-03T15:00:00Z', :CURRENCYCODE => 'JPY' } end context 'when start_date is Time' do it 'should be stringfy' do instance = Paypal::Payment::Recurring.new attributes.merge( :start_date => Time.utc(2011, 2, 8, 15, 0, 0) ) instance.start_date.should be_instance_of(Time) instance.to_params[:PROFILESTARTDATE].should == '2011-02-08 15:00:00' end end context 'when optional trial setting given' do let :trial_attributes do { :period => 'Month', :frequency => '1', :total_cycles => '0', :currency_code => 'JPY', :amount => '1000', :shipping_amount => '0', :tax_amount => '0' } end it 'should setup trial billing info' do instance.to_params.should == { :TRIALBILLINGPERIOD => "Month", :TRIALBILLINGFREQUENCY => 1, :TRIALTOTALBILLINGCYCLES => 0, :TRIALAMT => "1000.00", :TRIALCURRENCYCODE => "JPY", :TRIALSHIPPINGAMT => "0.00", :TRIALTAXAMT => "0.00", :BILLINGPERIOD => "Month", :BILLINGFREQUENCY => 1, :TOTALBILLINGCYCLES => 0, :AMT => "1000.00", :CURRENCYCODE => "JPY", :SHIPPINGAMT => "0.00", :TAXAMT => "0.00", :DESC => "Subscription Payment Profile", :MAXFAILEDPAYMENTS => 0, :AUTOBILLOUTAMT => "NoAutoBill", :PROFILESTARTDATE => "2011-02-03T15:00:00Z", :SUBSCRIBERNAME => "Nov Matake" } end end end describe '#numeric_attribute?' do let :numeric_attributes do [:aggregate_amount, :aggregate_optional_amount, :max_fails, :failed_count] end it 'should detect numeric attributes' do numeric_attributes.each do |key| instance.numeric_attribute?(key).should be_true end non_numeric_keys = keys - numeric_attributes non_numeric_keys.each do |key| instance.numeric_attribute?(key).should be_false end end end end ================================================ FILE: spec/paypal/payment/request/item_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Request::Item do let :instance do Paypal::Payment::Request::Item.new( :name => 'Name', :description => 'Description', :amount => 10, :quantity => 5, :category => :Digital, :number => '1' ) end describe '#to_params' do it 'should handle Recurring Profile activation parameters' do instance.to_params(1).should == { :L_PAYMENTREQUEST_1_NAME0 => 'Name', :L_PAYMENTREQUEST_1_DESC0 => 'Description', :L_PAYMENTREQUEST_1_AMT0 => '10.00', :L_PAYMENTREQUEST_1_QTY0 => 5, :L_PAYMENTREQUEST_1_ITEMCATEGORY0 => :Digital, :L_PAYMENTREQUEST_1_NUMBER0 => '1' } end end end ================================================ FILE: spec/paypal/payment/request_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Request do let :instant_request do Paypal::Payment::Request.new( :amount => 25.7, :tax_amount => 0.4, :shipping_amount => 1.5, :currency_code => :JPY, :description => 'Instant Payment Request', :notify_url => 'http://merchant.example.com/notify', :invoice_number => 'ABC123', :custom => 'Custom', :items => [{ :quantity => 2, :name => 'Item1', :description => 'Awesome Item 1!', :amount => 10.25 }, { :quantity => 3, :name => 'Item2', :description => 'Awesome Item 2!', :amount => 1.1 }], :custom_fields => { "l_surveychoice{n}" => 'abcd' # The '{n}' will be replaced with the index } ) end let :recurring_request do Paypal::Payment::Request.new( :currency_code => :JPY, :billing_type => :RecurringPayments, :billing_agreement_description => 'Recurring Payment Request' ) end let :reference_transaction_request do Paypal::Payment::Request.new( :currency_code => :JPY, :billing_type => :MerchantInitiatedBillingSingleAgreement, :billing_agreement_description => 'Reference Transaction Request' ) end describe '.new' do it 'should handle Instant Payment parameters' do instant_request.amount.total.should == 25.7 instant_request.amount.tax.should == 0.4 instant_request.amount.shipping.should == 1.5 instant_request.currency_code.should == :JPY instant_request.description.should == 'Instant Payment Request' instant_request.notify_url.should == 'http://merchant.example.com/notify' end it 'should handle Recurring Payment parameters' do recurring_request.currency_code.should == :JPY recurring_request.billing_type.should == :RecurringPayments recurring_request.billing_agreement_description.should == 'Recurring Payment Request' end it 'should handle Recurring Payment parameters' do reference_transaction_request.currency_code.should == :JPY reference_transaction_request.billing_type.should == :MerchantInitiatedBillingSingleAgreement reference_transaction_request.billing_agreement_description.should == 'Reference Transaction Request' end end describe '#to_params' do it 'should handle Instant Payment parameters' do instant_request.to_params.should == { :PAYMENTREQUEST_0_AMT => "25.70", :PAYMENTREQUEST_0_TAXAMT => "0.40", :PAYMENTREQUEST_0_SHIPPINGAMT => "1.50", :PAYMENTREQUEST_0_CURRENCYCODE => :JPY, :PAYMENTREQUEST_0_DESC => "Instant Payment Request", :PAYMENTREQUEST_0_NOTIFYURL => "http://merchant.example.com/notify", :PAYMENTREQUEST_0_ITEMAMT => "23.80", :PAYMENTREQUEST_0_INVNUM => "ABC123", :PAYMENTREQUEST_0_CUSTOM => "Custom", :L_PAYMENTREQUEST_0_NAME0 => "Item1", :L_PAYMENTREQUEST_0_DESC0 => "Awesome Item 1!", :L_PAYMENTREQUEST_0_AMT0 => "10.25", :L_PAYMENTREQUEST_0_QTY0 => 2, :L_PAYMENTREQUEST_0_NAME1 => "Item2", :L_PAYMENTREQUEST_0_DESC1 => "Awesome Item 2!", :L_PAYMENTREQUEST_0_AMT1 => "1.10", :L_PAYMENTREQUEST_0_QTY1 => 3, :L_SURVEYCHOICE0 => 'abcd' # Note the 'n' was replaced by the index } end it 'should handle Recurring Payment parameters' do recurring_request.to_params.should == { :PAYMENTREQUEST_0_AMT => "0.00", :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00", :PAYMENTREQUEST_0_CURRENCYCODE => :JPY, :L_BILLINGTYPE0 => :RecurringPayments, :L_BILLINGAGREEMENTDESCRIPTION0 => "Recurring Payment Request" } end it 'should handle Reference Transactions parameters' do reference_transaction_request.to_params.should == { :PAYMENTREQUEST_0_AMT => "0.00", :PAYMENTREQUEST_0_TAXAMT => "0.00", :PAYMENTREQUEST_0_SHIPPINGAMT => "0.00", :PAYMENTREQUEST_0_CURRENCYCODE => :JPY, :L_BILLINGTYPE0 => :MerchantInitiatedBillingSingleAgreement, :L_BILLINGAGREEMENTDESCRIPTION0 => "Reference Transaction Request" } end end describe '#items_amount' do context 'when BigDecimal' let(:instance) do Paypal::Payment::Request.new( :items => [{ :quantity => 3, :name => 'Item1', :description => 'Awesome Item 1!', :amount => 130.45 }] ) end # NOTE: # 130.45 * 3 => 391.34999999999997 (in ruby 1.9) it 'should calculate total amount correctly' do instance.items_amount.should == 391.35 end end end ================================================ FILE: spec/paypal/payment/response/address_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Response::Address do let :keys do Paypal::Payment::Response::Address.optional_attributes end describe '.new' do it 'should allow nil for attributes' do payer = Paypal::Payment::Response::Address.new keys.each do |key| payer.send(key).should be_nil end end it 'should treat all attributes as String' do attributes = keys.inject({}) do |attributes, key| attributes.merge!(key => "xyz") end payer = Paypal::Payment::Response::Address.new attributes keys.each do |key| payer.send(key).should == "xyz" end end end end ================================================ FILE: spec/paypal/payment/response/info_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Response::Info do let :attribute_mapping do Paypal::Payment::Response::Info.attribute_mapping end let :attributes do { :ACK => 'Success', :CURRENCYCODE => 'JPY', :ERRORCODE => 0, :ORDERTIME => '2011-02-08T03:23:54Z', :PAYMENTSTATUS => 'Completed', :PAYMENTTYPE => 'instant', :PENDINGREASON => 'None', :PROTECTIONELIGIBILITY => 'Ineligible', :PROTECTIONELIGIBILITYTYPE => 'None', :REASONCODE => 'None', :TRANSACTIONID => '8NC65222871997739', :TRANSACTIONTYPE => 'expresscheckout', :AMT => '14.00', :FEEAMT => '0.85', :TAXAMT => '0.00', :RECEIPTID => '12345', :SECUREMERCHANTACCOUNTID => '123456789', :PAYMENTREQUESTID => '12345', :SELLERPAYPALACCOUNTID => 'seller@shop.example.com', :EXCHANGERATE => '0.811965' } end describe '.new' do context 'when attribute keys are uppercase Symbol' do it 'should accept all without any warning' do Paypal.logger.should_not_receive(:warn) from_symbol_uppercase = Paypal::Payment::Response::Info.new attributes attribute_mapping.values.each do |key| from_symbol_uppercase.send(key).should_not be_nil end from_symbol_uppercase.amount.should == Paypal::Payment::Common::Amount.new( :total => 14, :fee => 0.85 ) end end context 'when attribute keys are lowercase Symbol' do it 'should ignore them and warn' do _attrs_ = attributes.inject({}) do |_attrs_, (k, v)| _attrs_.merge!(k.to_s.downcase.to_sym => v) end _attrs_.each do |key, value| Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Payment::Response::Info): #{key}=#{value}" ) end from_symbol_lowercase = Paypal::Payment::Response::Info.new _attrs_ attribute_mapping.values.each do |key| from_symbol_lowercase.send(key).should be_nil end from_symbol_lowercase.amount.should == Paypal::Payment::Common::Amount.new end end context 'when attribute keys are String' do it 'should ignore them and warn' do attributes.stringify_keys.each do |key, value| Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Payment::Response::Info): #{key}=#{value}" ) end from_string = Paypal::Payment::Response::Info.new attributes.stringify_keys attribute_mapping.values.each do |key| from_string.send(key).should be_nil end from_string.amount.should == Paypal::Payment::Common::Amount.new end end context 'when non-supported attributes are given' do it 'should ignore them and warn' do _attr_ = attributes.merge( :ignored => 'Ignore me!' ) Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Payment::Response::Info): ignored=Ignore me!" ) Paypal::Payment::Response::Info.new _attr_ end end end end ================================================ FILE: spec/paypal/payment/response/item_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Response::Info do let :attributes do { :NAME => 'Item Name', :DESC => 'Item Description', :QTY => '1', :NUMBER => '1', :ITEMCATEGORY => 'Digital', :ITEMWIDTHVALUE => '1.0', :ITEMHEIGHTVALUE => '2.0', :ITEMLENGTHVALUE => '3.0', :ITEMWEIGHTVALUE => '4.0' } end describe '.new' do subject { Paypal::Payment::Response::Item.new(attributes) } its(:name) { should == 'Item Name' } its(:description) { should == 'Item Description' } its(:quantity) { should == 1 } its(:category) { should == 'Digital' } its(:width) { should == '1.0' } its(:height) { should == '2.0' } its(:length) { should == '3.0' } its(:weight) { should == '4.0' } its(:number) { should == '1' } context 'when non-supported attributes are given' do it 'should ignore them and warn' do _attr_ = attributes.merge( :ignored => 'Ignore me!' ) Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Payment::Response::Item): ignored=Ignore me!" ) Paypal::Payment::Response::Item.new _attr_ end end end end ================================================ FILE: spec/paypal/payment/response/payer_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Response::Payer do let :keys do Paypal::Payment::Response::Payer.optional_attributes end describe '.new' do it 'should allow nil for attributes' do payer = Paypal::Payment::Response::Payer.new keys.each do |key| payer.send(key).should be_nil end end it 'should treat all attributes as String' do attributes = keys.inject({}) do |attributes, key| attributes.merge!(key => "xyz") end payer = Paypal::Payment::Response::Payer.new attributes keys.each do |key| payer.send(key).should == "xyz" end end end end ================================================ FILE: spec/paypal/payment/response_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Payment::Response do describe '.new' do context 'when non-supported attributes are given' do it 'should ignore them and warn' do Paypal.logger.should_receive(:warn).with( "Ignored Parameter (Paypal::Payment::Response): ignored=Ignore me!" ) response = Paypal::Payment::Response.new( :ignored => 'Ignore me!' ) end end end end ================================================ FILE: spec/paypal/util_spec.rb ================================================ require 'spec_helper.rb' describe Paypal::Util do describe '.formatted_amount' do it 'should return String in "xx.yy" format' do Paypal::Util.formatted_amount(nil).should == '0.00' Paypal::Util.formatted_amount(10).should == '10.00' Paypal::Util.formatted_amount(10.02).should == '10.02' Paypal::Util.formatted_amount(10.2).should == '10.20' Paypal::Util.formatted_amount(10.24).should == '10.24' Paypal::Util.formatted_amount(10.255).should == '10.25' end end describe '.to_numeric' do it 'should return Numeric' do Paypal::Util.to_numeric('10').should be_kind_of(Integer) Paypal::Util.to_numeric('10.5').should be_kind_of(Float) Paypal::Util.to_numeric('-1.5').should == -1.5 Paypal::Util.to_numeric('-1').should == -1 Paypal::Util.to_numeric('0').should == 0 Paypal::Util.to_numeric('0.00').should == 0 Paypal::Util.to_numeric('10').should == 10 Paypal::Util.to_numeric('10.00').should == 10 Paypal::Util.to_numeric('10.02').should == 10.02 Paypal::Util.to_numeric('10.2').should == 10.2 Paypal::Util.to_numeric('10.20').should == 10.2 Paypal::Util.to_numeric('10.24').should == 10.24 Paypal::Util.to_numeric('10.25').should == 10.25 end end end ================================================ FILE: spec/spec_helper.rb ================================================ require 'simplecov' SimpleCov.start do add_filter 'spec' end require 'paypal' require 'rspec' require 'helpers/fake_response_helper' RSpec.configure do |config| config.before do Paypal.logger = double("logger") end config.after do FakeWeb.clean_registry end end def sandbox_mode(&block) Paypal.sandbox! yield ensure Paypal.sandbox = false end