├── .gitignore ├── Gemfile ├── LICENSE ├── README.markdown ├── Rakefile ├── cacert.pem ├── config └── paypal_adaptive.yml ├── lib ├── pay_request_schema.json ├── paypal_adaptive.rb └── paypal_adaptive │ ├── config.rb │ ├── ipn_notification.rb │ ├── request.rb │ ├── response.rb │ └── version.rb ├── paypal_adaptive.gemspec ├── templates └── paypal_ipn.rb └── test ├── data ├── invalid_chain_pay_request.json ├── invalid_parallel_pay_request.json ├── invalid_preapproval.json ├── invalid_preapproval_error_execstatus.json ├── invalid_preapproval_payment.json ├── invalid_simple_pay_request_1.json ├── valid_chain_pay_request.json ├── valid_get_payment_options_request.json ├── valid_get_shipping_addresses_request.json ├── valid_parallel_pay_request.json ├── valid_payment_details_request.json ├── valid_preapproval.json ├── valid_set_payment_options_request.json ├── valid_simple_pay_request_1.json └── verified_get_verified_status_request.json ├── test_helper.rb └── unit ├── config_test.rb ├── get_shipping_addresses_test.rb ├── get_verified_status_test.rb ├── pay_request_schema_test.rb ├── pay_request_test.rb ├── payment_details_test.rb ├── payment_options_test.rb ├── preapproval_test.rb └── request_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | 21 | ## PROJECT::SPECIFIC 22 | config 23 | Gemfile.lock 24 | .rvmrc 25 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Tommy Chheng 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # paypal_adaptive 2 | This gem is a lightweight wrapper for the paypal adaptive payments API. 3 | 4 | This is very much a work in progress! Use at your own risk or submit bug fixes :) 5 | 6 | Before you need start, read the API manual https://www.x.com/docs/DOC-1531 and check out the forums on http://x.com 7 | It'll be invaluable for parameters and error messages. The gem keeps the request/response as hashes so you will have to 8 | read the manual to make the proper calls. I made a few test cases for further examples at http://github.com/tc/paypal_adaptive/tree/master/test/ 9 | 10 | 11 | ## HOWTO 12 | Create paypal_adaptive.yml to your config folder: 13 | 14 | development: 15 | environment: "sandbox" 16 | username: "sandbox_username" 17 | password: "sandbox_password" 18 | signature: "sandbox_signature" 19 | application_id: "sandbox_app_id" 20 | api_cert_file: 21 | 22 | test: 23 | environment: "sandbox" 24 | username: "sandbox_username" 25 | password: "sandbox_password" 26 | signature: "sandbox_signature" 27 | application_id: "sandbox_app_id" 28 | api_cert_file: "/path/to_cert" 29 | 30 | production: 31 | environment: "production" 32 | username: "my_production_username" 33 | password: "my_production_password" 34 | signature: "my_production_signature" 35 | application_id: "my_production_app_id" 36 | api_cert_file: "/path/to_cert" 37 | 38 | You can also use ENV variables when specifying your configuration. eg. 39 | ```<%= ENV[''paypal.username'] %>``` 40 | 41 | Make the payment request: 42 | 43 | pay_request = PaypalAdaptive::Request.new 44 | 45 | data = { 46 | "returnUrl" => "http://testserver.com/payments/completed_payment_request", 47 | "requestEnvelope" => {"errorLanguage" => "en_US"}, 48 | "currencyCode"=>"USD", 49 | "receiverList"=>{"receiver"=>[{"email"=>"testpp_1261697850_per@nextsprocket.com", "amount"=>"10.00"}]}, 50 | "cancelUrl"=>"http://testserver.com/payments/canceled_payment_request", 51 | "actionType"=>"PAY", 52 | "ipnNotificationUrl"=>"http://testserver.com/payments/ipn_notification" 53 | } 54 | 55 | pay_response = pay_request.pay(data) 56 | 57 | if pay_response.success? 58 | redirect_to pay_response.preapproval_paypal_payment_url 59 | else 60 | puts pay_response.errors.first['message'] 61 | redirect_to failed_payment_url 62 | end 63 | 64 | --- 65 | Once the user goes to pay_response.approve_paypal_payment_url, they will be prompted to login to Paypal for payment. 66 | 67 | Upon payment completion page, they will be redirected to http://testserver.com/payments/completed_payment_request. 68 | 69 | They can also click cancel to go to http://testserver.com/payments/canceled_payment_request 70 | 71 | The actual payment details will be sent to your server via "ipnNotificationUrl" 72 | You have to create a listener to receive POST messages from paypal. I added a Rails metal template in the templates folder which handles the callback. 73 | 74 | Additionally, you can make calls to Paypal Adaptive's other APIs: 75 | payment_details, preapproval, preapproval_details, cancel_preapproval, convert_currency, refund 76 | 77 | Input is just a Hash just like the pay method. Refer to the Paypal manual for more details. 78 | 79 | ### Using the embedded payment flow 80 | Instead of redirecting to the url from ```redirect_to pay_response.approve_paypal_payment_url``` you can generate the action url for your 81 | form by using ```pay_response.approve_paypal_payment_url 'MY TYPE' ``` The two types that are supported for embedded payment are 'light' and 'mini' 82 | More information about these types can be found here https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_APIntro 83 | 84 | ### Certificate validation 85 | You can set the location of the key file you downloaded from PayPal, in paypal_adaptive.yml 86 | for each environment, e.g.: 87 | 88 | development: 89 | environment: "sandbox" 90 | username: "sandbox_username" 91 | password: "sandbox_password" 92 | signature: "sandbox_signature" 93 | application_id: "sandbox_app_id" 94 | api_cert_file: "/path/to/your/private.key" 95 | 96 | The api_cert_file should point to your cert_key_pem.txt that is downloaded through the paypal developer interface. It will contain a section that specifies the RSA private key and another section that specifies a certificate. If this is left empty, paypal_adaptive will attempt to use the signature method of validation with PayPal, so your signature config must not be nil. 97 | 98 | ## Changelog 99 | 0.3.11 100 | Remove `json` from dependencies 101 | 102 | 0.3.10 103 | Remove Rails dependent gems thanks @ rchekaluk 104 | 105 | 0.3.9 106 | Ensure POST response is not decompressed under Ruby 2.0 thanks @rchekaluk 107 | 108 | 0.3.8 109 | Fix IPN send back data. thanks @maxbeizer 110 | 111 | 0.3.7 112 | Fix IPN under Ruby 2.0. thanks @rchekaluk 113 | 114 | 0.3.6 115 | Include more details in error in case the HTTP request doesn't return. thanks mreinsch 116 | 117 | 0.3.5 118 | SSL verify mode set to none for sandbox testing. thanks @mikong 119 | 120 | 0.3.4 121 | Locale specific paypal urls and improved testing. changed approve_paypal_payment_url method signature with deprecation warning. thanks mreinsch. 122 | 123 | 0.3.3 124 | Handle JSON parsing error. Added validation for config. 125 | 126 | 0.3.2 127 | Added support to api certificate since ssl_cert_file config is now used to set CA Authority. thanks jimbocortes 128 | 129 | 0.3.1 130 | Update json dependency to use ~>1.0 so any version 1.X will work. 131 | 132 | 0.3.0 133 | ssl_cert_file fixes from eddroid. get_verified_status function from bundacia. 134 | 135 | 0.2.9 136 | Fixed cert issues in last release. Exception handling for connection. 137 | 138 | 0.2.8 139 | Cert fixes and ruby 1.9.3 files. Thanks tonyla. 140 | 141 | 0.2.7 142 | Refactored config file handling for better non-Rails support, thanks mreinsch and dave-thompson. 143 | 144 | 0.2.6 145 | Fix for using correct status field in pre-approval, thanks nbibler. Fix for ruby 1.9.3, thanks deepj. 146 | 147 | 0.2.5 148 | Fix for embedded payments, thanks rafaelivan. Fix for Rails 3.1.1, thanks synth. 149 | 150 | 0.2.4 151 | Support for embedded payments. Issue #21 thanks rafaelivan. Shipping address, wrapper methods has return response object. Issue #22 thanks tsmango. 152 | 153 | 0.2.3 154 | Using bundler for gem creation. Moved all code to paypal_adaptive dir. Added ExecutePayment call to request. Thanks Roger Neel. 155 | 156 | 0.2.2 157 | Added support for ERB in the config file. Thanks DanielVartanov. 158 | 159 | 0.2.1 160 | Fixed SSL bug. Thanks gaelian. 161 | 162 | 0.2.0 163 | Thanks to seangaffney for set payment option support. 164 | Thanks to gaelian for ssl cert support. 165 | Changed tests to use relative paths. 166 | 167 | 0.1.0 168 | Fixed IPN rails metal template by sending the correct params back: ipn.send_back(env['rack.request.form_vars']) 169 | Thanks to github.com/JoN1oP for fixing this. 170 | 171 | 0.0.5 172 | Added Preapproval preapproval_paypal_payment_url along with test case. 173 | 174 | 0.0.4 175 | Preapproval now returns a PaypalAdaptive::Response class. Added preapproval test cases. 176 | 177 | 0.0.3 178 | Renamed PayRequest, PayResponse into Request, Response since other api calls use the class as well. 179 | 180 | 0.0.2 181 | Fixed initialized constant warning. 182 | 183 | 0.0.1 184 | First release. 185 | 186 | ## Copyright 187 | 188 | Copyright (c) 2009 Tommy Chheng. See LICENSE for details. 189 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rake/testtask' 3 | 4 | Bundler::GemHelper.install_tasks 5 | Dir[File.join(File.dirname(__FILE__), "lib", "tasks", "**", "*.rake")].each { |ext| load ext } 6 | 7 | task :default => :test 8 | 9 | task :test => %w(test:units) 10 | namespace :test do 11 | desc "run unit tests" 12 | Rake::TestTask.new(:units) do |test| 13 | test.libs << 'lib' << 'test' 14 | test.test_files = FileList["test/unit/*_test.rb", "test/unit/*/*_test.rb"] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/paypal_adaptive.yml: -------------------------------------------------------------------------------- 1 | development: 2 | environment: "sandbox" 3 | username: "signupforuseratsandboxpaypal" 4 | password: "signupforuseratsandboxpaypal" 5 | signature: "signupforuseratsandboxpaypal" 6 | application_id: "APP-TEST" 7 | api_cert_file: "/path/to_cert" 8 | 9 | test: 10 | environment: "sandbox" 11 | username: "signupforuseratsandboxpaypal" 12 | password: "signupforuseratsandboxpaypal" 13 | signature: "signupforuseratsandboxpaypal" 14 | application_id: "APP-TEST" 15 | api_cert_file: "/path/to_cert" 16 | 17 | production: 18 | environment: "production" 19 | username: "my_production_username" 20 | password: "my_production_password" 21 | signature: "my_production_signature" 22 | application_id: "my_production_app_id" 23 | api_cert_file: "/path/to_cert" 24 | 25 | with_erb_tags: 26 | environment: "sandbox" 27 | username: <%= ENV['paypal.username'] %> 28 | password: <%= ENV['paypal.password'] %> 29 | signature: "signupforuseratsandboxpaypal" 30 | application_id: "APP-TEST" 31 | api_cert_file: "/path/to_cert" 32 | -------------------------------------------------------------------------------- /lib/pay_request_schema.json: -------------------------------------------------------------------------------- 1 | {"description":"Paypal PayRequest API", 2 | "type":"object", 3 | "properties": 4 | { 5 | "actionType": {"type":"string", "options":[{"value":"PAY", "label":"PAY"}]}, 6 | "cancelUrl": {"type":"string"}, 7 | "clientDetails": {"type":"object", "optional": true}, 8 | "currencyCode": {"type":"string"}, 9 | "feesPayer": {"type":"string", "optional":true, 10 | "options":[ 11 | {"value":"SENDER", "label":"SENDER"}, 12 | {"value":"PRIMARYRECEIVER", "label":"PRIMARYRECEIVER"}, 13 | {"value":"EACHRECEIVER", "label":"EACHRECEIVER"}, 14 | {"value":"SECONDARYONLY", "label":"SECONDARYONLY"} 15 | ]}, 16 | "fundingConstraint": {"type":"object", "optional": true}, 17 | "ipnNotificationUrl": {"type":"string", "optional": true}, 18 | "memo": {"type":"string", "optional": true}, 19 | "pin": {"type":"string", "optional": true}, 20 | "preapprovalKey": {"type":"string", "optional": true}, 21 | "receiverList": { 22 | "type":"object", "properties":{ 23 | "receiver":{ 24 | "type":"array", 25 | "items":{ 26 | "email":{"type":"string"}, 27 | "amount":{"type":"string"}, 28 | "primary":{"type":"string","optional": true}} 29 | } 30 | } 31 | }, 32 | 33 | "requestEnvelope": {"type":"object", "properties":{"errorLanguage":{"type":"string"}}}, 34 | "returnUrl": {"type":"string"}, 35 | "reverseAllParallelPaymentsOnError": {"type":"boolean", "optional": true}, 36 | "senderEmail": {"type":"string", "optional": true}, 37 | "trackingId": {"type":"string", "optional": true} 38 | } 39 | } -------------------------------------------------------------------------------- /lib/paypal_adaptive.rb: -------------------------------------------------------------------------------- 1 | require 'paypal_adaptive/config' 2 | require 'paypal_adaptive/request' 3 | require 'paypal_adaptive/response' 4 | require 'paypal_adaptive/ipn_notification' 5 | 6 | module PaypalAdaptive 7 | end 8 | -------------------------------------------------------------------------------- /lib/paypal_adaptive/config.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'erb' 3 | 4 | module PaypalAdaptive 5 | class Config 6 | PAYPAL_BASE_URL_MAPPING = { 7 | :production => "https://www.paypal.com", 8 | :sandbox => "https://www.sandbox.paypal.com", 9 | :beta_sandbox => "https://www.beta-sandbox.paypal.com" 10 | } unless defined? PAYPAL_BASE_URL_MAPPING 11 | 12 | API_BASE_URL_MAPPING = { 13 | :production => "https://svcs.paypal.com", 14 | :sandbox => "https://svcs.sandbox.paypal.com", 15 | :beta_sandbox => "https://svcs.beta-sandbox.paypal.com" 16 | } unless defined? API_BASE_URL_MAPPING 17 | 18 | attr_accessor :paypal_base_url, :api_base_url, :headers, :ssl_cert_path, :ssl_cert_file, :api_cert_file, 19 | :verify_mode 20 | 21 | def initialize(env=nil, config_override={}) 22 | config = YAML.load(ERB.new(File.new(config_filepath).read).result)[env] 23 | raise "Could not load settings from config file" unless config 24 | config.merge!(config_override) unless config_override.nil? 25 | 26 | validate_config(config) 27 | 28 | if config["retain_requests_for_test"] == true 29 | @retain_requests_for_test = true 30 | else 31 | pp_env = config['environment'].to_sym 32 | 33 | @ssl_cert_path = nil 34 | @ssl_cert_file = nil 35 | @api_cert_file = nil 36 | @paypal_base_url = PAYPAL_BASE_URL_MAPPING[pp_env] 37 | @api_base_url = API_BASE_URL_MAPPING[pp_env] 38 | 39 | # http.rb requires headers to be strings. Protect against ints in paypal_adaptive.yml 40 | config.update(config){ |key,v| v.to_s } 41 | @headers = { 42 | "X-PAYPAL-SECURITY-USERID" => config['username'], 43 | "X-PAYPAL-SECURITY-PASSWORD" => config['password'], 44 | "X-PAYPAL-APPLICATION-ID" => config['application_id'], 45 | "X-PAYPAL-REQUEST-DATA-FORMAT" => "JSON", 46 | "X-PAYPAL-RESPONSE-DATA-FORMAT" => "JSON" 47 | } 48 | @headers.merge!({"X-PAYPAL-SECURITY-SIGNATURE" => config['signature']}) if config['signature'] 49 | @ssl_cert_path = config['ssl_cert_path'] unless config['ssl_cert_path'].nil? || config['ssl_cert_path'].length == 0 50 | @ssl_cert_file = config['ssl_cert_file'] unless config['ssl_cert_file'].nil? || config['ssl_cert_file'].length == 0 51 | @api_cert_file = config['api_cert_file'] unless config['api_cert_file'].nil? || config['api_cert_file'].length == 0 52 | @verify_mode = if pp_env == :sandbox 53 | OpenSSL::SSL::VERIFY_NONE 54 | else 55 | OpenSSL::SSL::VERIFY_PEER 56 | end 57 | end 58 | end 59 | 60 | def validate_config(config) 61 | raise "No username in paypal_adaptive.yml specified." unless config['username'] 62 | raise "No password in paypal_adaptive.yml specified." unless config['password'] 63 | raise "No application_id in paypal_adaptive.yml specified." unless config['application_id'] 64 | 65 | true 66 | end 67 | 68 | def config_filepath 69 | if defined?(Rails) 70 | Rails.root.join("config", "paypal_adaptive.yml") 71 | else 72 | File.join(File.dirname(__FILE__), "..", "..", "config", "paypal_adaptive.yml") 73 | end 74 | end 75 | 76 | def retain_requests_for_test? 77 | !!@retain_requests_for_test 78 | end 79 | end 80 | 81 | def self.config(env = nil) 82 | env ||= default_env_for_config 83 | raise "Please provide an environment" unless env 84 | @configs ||= Hash.new 85 | @configs[env] ||= Config.new(env) 86 | end 87 | 88 | private 89 | 90 | def self.default_env_for_config 91 | defined?(Rails) ? Rails.env : nil 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/paypal_adaptive/ipn_notification.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'net/https' 3 | require 'json' 4 | require 'rack/utils' 5 | 6 | module PaypalAdaptive 7 | class IpnNotification 8 | 9 | def initialize(env=nil) 10 | config = PaypalAdaptive.config(env) 11 | @paypal_base_url = config.paypal_base_url 12 | @ssl_cert_path = config.ssl_cert_path 13 | @ssl_cert_file = config.ssl_cert_file 14 | @api_cert_file = config.api_cert_file 15 | @verify_mode = config.verify_mode 16 | end 17 | 18 | def send_back(data) 19 | data = "cmd=_notify-validate&#{data}" 20 | path = "#{@paypal_base_url}/cgi-bin/webscr" 21 | url = URI.parse path 22 | http = Net::HTTP.new(url.host, 443) 23 | http.use_ssl = true 24 | http.verify_mode = @verify_mode 25 | http.ca_path = @ssl_cert_path unless @ssl_cert_path.nil? 26 | 27 | if @api_cert_file 28 | cert = File.read(@api_cert_file) 29 | http.cert = OpenSSL::X509::Certificate.new(cert) 30 | http.key = OpenSSL::PKey::RSA.new(cert) 31 | end 32 | http.ca_path = @ssl_cert_path unless @ssl_cert_path.nil? || @ssl_cert_path.length == 0 33 | http.ca_file = @ssl_cert_file unless @ssl_cert_file.nil? || @ssl_cert_file.length == 0 34 | 35 | req = Net::HTTP::Post.new(url.request_uri) 36 | # we don't want #set_form_data to create a hash and get our 37 | # response out of order; Paypal IPN docs explicitly state that 38 | # the contents of #send_back must be in the same order as they 39 | # were recieved 40 | req.body = data 41 | req.content_type = 'application/x-www-form-urlencoded' 42 | req['Accept-Encoding'] = 'identity' 43 | response_data = http.request(req).body 44 | 45 | @verified = response_data == "VERIFIED" 46 | end 47 | 48 | def verified? 49 | @verified 50 | end 51 | 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/paypal_adaptive/request.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'net/http' 3 | require 'net/https' 4 | 5 | module PaypalAdaptive 6 | class NoDataError < Exception 7 | end 8 | 9 | class Request 10 | def initialize(env = nil) 11 | @env = env 12 | config = PaypalAdaptive.config(env) 13 | @api_base_url = config.api_base_url 14 | @headers = config.headers 15 | @headers['Accept-Encoding'] = 'identity' 16 | @ssl_cert_path = config.ssl_cert_path 17 | @ssl_cert_file = config.ssl_cert_file 18 | @api_cert_file = config.api_cert_file 19 | @verify_mode = config.verify_mode 20 | end 21 | 22 | def validate 23 | #TODO the receiverList field not validating properly 24 | 25 | # @schema_filepath = "../lib/pay_request_schema.json" 26 | # @schema = File.open(@schema_filepath, "rb"){|f| JSON.parse(f.read)} 27 | # see page 42 of PP Adaptive Payments PDF for explanation of all fields. 28 | #JSON::Schema.validate(@data, @schema) 29 | end 30 | 31 | def pay(data) 32 | wrap_post(data, "/AdaptivePayments/Pay") 33 | end 34 | 35 | def payment_details(data) 36 | wrap_post(data, "/AdaptivePayments/PaymentDetails") 37 | end 38 | 39 | def set_payment_options(data) 40 | wrap_post(data, "/AdaptivePayments/SetPaymentOptions") 41 | end 42 | 43 | def get_payment_options(data) 44 | wrap_post(data, "/AdaptivePayments/GetPaymentOptions") 45 | end 46 | 47 | def get_shipping_addresses(data) 48 | wrap_post(data, "/AdaptivePayments/GetShippingAddresses") 49 | end 50 | 51 | def preapproval(data) 52 | wrap_post(data, "/AdaptivePayments/Preapproval") 53 | end 54 | 55 | def preapproval_details(data) 56 | wrap_post(data, "/AdaptivePayments/PreapprovalDetails") 57 | end 58 | 59 | def cancel_preapproval(data) 60 | wrap_post(data, "/AdaptivePayments/CancelPreapproval") 61 | end 62 | 63 | def convert_currency(data) 64 | wrap_post(data, "/AdaptivePayments/ConvertCurrency") 65 | end 66 | 67 | def refund(data) 68 | wrap_post(data, "/AdaptivePayments/Refund") 69 | end 70 | 71 | def execute_payment(data) 72 | wrap_post(data, "/AdaptivePayments/ExecutePayment") 73 | end 74 | 75 | def get_verified_status(data) 76 | wrap_post(data, "/AdaptiveAccounts/GetVerifiedStatus") 77 | end 78 | 79 | def get_funding_plans(data) 80 | wrap_post(data, "/AdaptivePayments/GetFundingPlans") 81 | end 82 | 83 | def wrap_post(data, path) 84 | raise NoDataError unless data 85 | 86 | PaypalAdaptive::Response.new(post(data, path), @env) 87 | end 88 | 89 | def rescue_error_message(e, message = nil, response_data = nil) 90 | error = {"message" => message || e} 91 | if response_data 92 | error["details"] = { 93 | "httpCode" => response_data.code, 94 | "httpMessage" => response_data.message.to_s, 95 | "httpBody" => response_data.body } 96 | end 97 | {"responseEnvelope" => {"ack" => "Failure"}, "error" => [error]} 98 | end 99 | 100 | def post(data, path) 101 | #hack fix: JSON.unparse doesn't work in Rails 2.3.5; only {}.to_json does.. 102 | api_request_data = JSON.unparse(data) rescue data.to_json 103 | url = URI.parse @api_base_url 104 | http = Net::HTTP.new(url.host, 443) 105 | http.use_ssl = true 106 | http.verify_mode = @verify_mode 107 | 108 | if @api_cert_file 109 | cert = File.read(@api_cert_file) 110 | http.cert = OpenSSL::X509::Certificate.new(cert) 111 | http.key = OpenSSL::PKey::RSA.new(cert) 112 | end 113 | http.ca_path = @ssl_cert_path unless @ssl_cert_path.nil? || @ssl_cert_path.length == 0 114 | http.ca_file = @ssl_cert_file unless @ssl_cert_file.nil? || @ssl_cert_file.length == 0 115 | 116 | begin 117 | response_data = http.post(path, api_request_data, @headers) 118 | JSON.parse(response_data.body) 119 | rescue Net::HTTPBadGateway => e 120 | rescue_error_message(e, "Error reading from remote server.") 121 | rescue JSON::ParserError => e 122 | rescue_error_message(e, "Response is not in JSON format.", response_data) 123 | rescue Exception => e 124 | case e 125 | when Errno::ECONNRESET 126 | rescue_error_message(e, "Connection Reset. Request invalid URL.") 127 | else 128 | rescue_error_message(e, response_data) 129 | end 130 | end 131 | 132 | end 133 | end 134 | 135 | end 136 | -------------------------------------------------------------------------------- /lib/paypal_adaptive/response.rb: -------------------------------------------------------------------------------- 1 | module PaypalAdaptive 2 | class Response < Hash 3 | def initialize(response={}, env=nil) 4 | config = PaypalAdaptive.config(env) 5 | @paypal_base_url = config.paypal_base_url 6 | 7 | self.merge!(response) 8 | end 9 | 10 | def success? 11 | !! (self['responseEnvelope']['ack'].to_s =~ /^Success$/i && 12 | !(self['paymentExecStatus'].to_s =~ /^ERROR$/i)) 13 | end 14 | 15 | def errors 16 | if success? 17 | return [] 18 | else 19 | errors = self['error'] 20 | errors ||= self['payErrorList']['payError'].collect { |e| e['error'] } rescue nil 21 | errors 22 | end 23 | end 24 | 25 | def error_message 26 | message = errors.first['message'] rescue nil 27 | end 28 | 29 | # URL to redirect to in order for the user to approve the payment 30 | # 31 | # options: 32 | # * country: default country code for the user 33 | # * type: mini / light 34 | def approve_paypal_payment_url(opts = {}) 35 | if opts.is_a?(Symbol) || opts.is_a?(String) 36 | warn "[DEPRECATION] use approve_paypal_payment_url(:type => #{opts})" 37 | opts = {:type => opts} 38 | end 39 | return nil if self['payKey'].nil? 40 | 41 | if ['mini', 'light'].include?(opts[:type].to_s) 42 | "#{@paypal_base_url}/webapps/adaptivepayment/flow/pay?expType=#{opts[:type]}&paykey=#{self['payKey']}" 43 | else 44 | base = @paypal_base_url 45 | base = base + "/#{opts[:country]}" if opts[:country] 46 | "#{base}/webscr?cmd=_ap-payment&paykey=#{self['payKey']}" 47 | end 48 | end 49 | 50 | def preapproval_paypal_payment_url 51 | self['preapprovalKey'].nil? ? nil : "#{@paypal_base_url}/webscr?cmd=_ap-preapproval&preapprovalkey=#{self['preapprovalKey']}" 52 | end 53 | 54 | # workaround for rails 3.1.1, see https://github.com/tc/paypal_adaptive/issues/23 55 | def nested_under_indifferent_access 56 | self 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/paypal_adaptive/version.rb: -------------------------------------------------------------------------------- 1 | module PaypalAdaptive 2 | VERSION = "0.3.11" 3 | end 4 | 5 | -------------------------------------------------------------------------------- /paypal_adaptive.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "paypal_adaptive/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "paypal_adaptive" 7 | s.version = PaypalAdaptive::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Tommy Chheng"] 10 | s.email = ["tommy.chheng@gmail.com"] 11 | s.homepage = "http://github.com/tc/paypal_adaptive" 12 | s.summary = "Lightweight wrapper for Paypal's Adaptive Payments API" 13 | s.description = "Lightweight wrapper for Paypal's Adaptive Payments API" 14 | 15 | s.add_dependency("jsonschema", "~>2.0.0") 16 | s.add_development_dependency("rake") 17 | s.add_development_dependency("webmock") 18 | 19 | s.rubyforge_project = "paypal_adaptive" 20 | 21 | s.files = `git ls-files`.split("\n") 22 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 23 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 24 | s.require_paths = ["lib"] 25 | end 26 | -------------------------------------------------------------------------------- /templates/paypal_ipn.rb: -------------------------------------------------------------------------------- 1 | # Allow the metal piece to run in isolation 2 | require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) 3 | 4 | class PaypalIpn 5 | def self.call(env) 6 | if env["PATH_INFO"] =~ /^\/paypal_ipn/ 7 | request = Rack::Request.new(env) 8 | params = request.params 9 | 10 | ipn = PaypalAdaptive::IpnNotification.new 11 | ipn.send_back(env['rack.request.form_vars']) 12 | if ipn.verified? 13 | #mark transaction as completed in your DB 14 | output = "Verified." 15 | else 16 | output = "Not Verified." 17 | end 18 | 19 | [200, {"Content-Type" => "text/html"}, [output]] 20 | else 21 | [404, {"Content-Type" => "text/html"}, ["Not Found"]] 22 | end 23 | end 24 | 25 | end -------------------------------------------------------------------------------- /test/data/invalid_chain_pay_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "receiverList":{"receiver":[ 6 | {"email":"testpp_1261697850_per@nextsprocket.com", "amount":"50.00", "primary": "true"}, 7 | {"email":"sender_1261713739_per@nextsprocket.com", "amount":"100.00", "primary": "false"}, 8 | {"email":"ppsell_1261697921_biz@nextsprocket.com", "amount":"100.00", "primary": "false"} 9 | ]}, 10 | "cancelUrl":"http://127.0.0.1:3000/payments/cancelled_payment", 11 | "actionType":"PAY" 12 | } 13 | 14 | -------------------------------------------------------------------------------- /test/data/invalid_parallel_pay_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "receiverList":{"receiver":[ 6 | {"email":"dummy@account.com", "amount":"100.00"}, 7 | {"email":"dummy@account.com", "amount":"50.00"}, 8 | {"email":"dummy@account.com", "amount":"50.00"} 9 | ]}, 10 | "cancelUrl":"http://127.0.0.1:3000/payments/cancelled_payment", 11 | "actionType":"PAY" 12 | } 13 | 14 | -------------------------------------------------------------------------------- /test/data/invalid_preapproval.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment_request", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "cancelUrl":"http://127.0.0.1:3000/payments/canceled_payment_request", 6 | "actionType":"PAY", 7 | "maxTotalAmountOfAllPayments": "1500.00", 8 | "maxNumberOfPayments":"30", 9 | "startingDate":"2010-07-13T07sdf00.000Z", 10 | "endingDate":"2010-12-13T07dfg0000.000Z" 11 | } 12 | 13 | -------------------------------------------------------------------------------- /test/data/invalid_preapproval_error_execstatus.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseEnvelope":{ 3 | "timestamp":"2011-11-20T09:33:51.178-08:00", 4 | "ack":"Success", 5 | "correlationId":"XXXXX", 6 | "build":"2279004" 7 | }, 8 | "payKey":"AP-XXXXXXXXXXXXXXXXX", 9 | "paymentExecStatus":"ERROR", 10 | "payErrorList":{ 11 | "payError":[{ 12 | "receiver":{"amount":"25.0","email":"email@email.com"}, 13 | "error":{ 14 | "errorId":"580036", 15 | "domain":"PLATFORM", 16 | "severity":"Error", 17 | "category":"Application", 18 | "message":"This transaction cannot be processed. Please enter a valid credit card number and type" 19 | } 20 | }] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/data/invalid_preapproval_payment.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseEnvelope":{ 3 | "timestamp":"2011-11-28T08:02:09.070-08:00", 4 | "ack":"Success", 5 | "correlationId":"XXXXXXXXXXXXX", 6 | "build":"2279004"}, 7 | "payKey":"XX-XXXXXXXXXXXXXXXXX", 8 | "paymentExecStatus":"ERROR", 9 | "payErrorList":{ 10 | "payError":[{ 11 | "receiver":{ 12 | "amount":"25.0", 13 | "email":"test@test.test"}, 14 | "error":{ 15 | "errorId":"580036", 16 | "domain":"PLATFORM", 17 | "severity":"Error", 18 | "category":"Application", 19 | "message":"This transaction cannot be processed. Please enter a valid credit card number and type"} 20 | }] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/data/invalid_simple_pay_request_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "receiverList":{"receiver":[{"email":"testpp_1261697850_per@nextsprocket.com", "amount":"10.0"}]}, 6 | "cancelUrl":"http://127.0.0.1:3000/payments/canceled_payment", 7 | "actionType":1 8 | } 9 | -------------------------------------------------------------------------------- /test/data/valid_chain_pay_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment_request", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "receiverList":{"receiver":[ 6 | {"email":"testpp_1261697850_per@nextsprocket.com", "amount":"100.00", "primary": "true"}, 7 | {"email":"ppsell_1261697921_biz@nextsprocket.com", "amount":"90.00", "primary": "false"} 8 | ]}, 9 | "cancelUrl":"http://127.0.0.1:3000/payments/canceled_payment_request", 10 | "actionType":"PAY" 11 | } 12 | 13 | -------------------------------------------------------------------------------- /test/data/valid_get_payment_options_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "payKey": "REPLACE-WITH-VALID-PAY-KEY", 3 | 4 | "requestEnvelope": { 5 | "errorLanguage": "en_US" 6 | } 7 | } -------------------------------------------------------------------------------- /test/data/valid_get_shipping_addresses_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "payKey": "REPLACE-WITH-VALID-PAY-KEY", 3 | 4 | "requestEnvelope": { 5 | "errorLanguage": "en_US" 6 | } 7 | } -------------------------------------------------------------------------------- /test/data/valid_parallel_pay_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment_request", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "receiverList":{"receiver":[ 6 | {"email":"testpp_1261697850_per@nextsprocket.com", "amount":"100.00"}, 7 | {"email":"sender_1261713739_per@nextsprocket.com", "amount":"50.00"}, 8 | {"email":"ppsell_1261697921_biz@nextsprocket.com", "amount":"50.00"} 9 | ]}, 10 | "cancelUrl":"http://127.0.0.1:3000/payments/canceled_payment_request", 11 | "actionType":"PAY", 12 | "senderEmail": "a@test.com" 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/data/valid_payment_details_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "payKey": "REPLACE-WITH-VALID-PAY-KEY", 3 | 4 | "requestEnvelope": { 5 | "errorLanguage": "en_US" 6 | } 7 | } -------------------------------------------------------------------------------- /test/data/valid_preapproval.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment_request", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "cancelUrl":"http://127.0.0.1:3000/payments/canceled_payment_request", 6 | "maxTotalAmountOfAllPayments": "1500.00", 7 | "maxNumberOfPayments":"30", 8 | "startingDate":"2020-07-13T07:00:00.000Z", 9 | "endingDate":"2020-12-13T07:00:00.000Z" 10 | } 11 | 12 | -------------------------------------------------------------------------------- /test/data/valid_set_payment_options_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "payKey": "REPLACE-WITH-VALID-PAY-KEY", 3 | 4 | "senderOptions": { 5 | "requireShippingAddressSelection": "true" 6 | }, 7 | 8 | "receiverOptions": { 9 | "invoiceData": { 10 | "item": [ 11 | { 12 | "identifier": "1", 13 | "name": "Sample Product", 14 | "itemCount": "1", 15 | "itemPrice": "10.00", 16 | "price": "10.00" 17 | } 18 | ], 19 | "totalShipping": "0.00" 20 | }, 21 | "receiver": { 22 | "email": "testpp_1261697850_per@nextsprocket.com" 23 | } 24 | }, 25 | 26 | "requestEnvelope": { 27 | "errorLanguage": "en_US" 28 | } 29 | } -------------------------------------------------------------------------------- /test/data/valid_simple_pay_request_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnUrl":"http://127.0.0.1:3000/payments/completed_payment_request", 3 | "requestEnvelope":{"errorLanguage":"en_US"}, 4 | "currencyCode":"USD", 5 | "receiverList":{"receiver":[{"email":"testpp_1261697850_per@nextsprocket.com", "amount":"10.00"}]}, 6 | "cancelUrl":"http://127.0.0.1:3000/payments/canceled_payment_request", 7 | "actionType":"PAY", 8 | "ipnNotificationUrl":"http://127.0.0.1:3000/payments/ipn_notification" 9 | } 10 | -------------------------------------------------------------------------------- /test/data/verified_get_verified_status_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestEnvelope":{"errorLanguage":"en_US"}, 3 | "emailAddress": "foo@example.com", 4 | "firstName": "foo", 5 | "lastName": "bar", 6 | "matchCriteria": "NONE" 7 | } 8 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) 3 | 4 | require "rubygems" 5 | require "test/unit" 6 | require "json" 7 | require "jsonschema" 8 | require 'paypal_adaptive' 9 | require 'webmock/minitest' 10 | 11 | WebMock.allow_net_connect! 12 | -------------------------------------------------------------------------------- /test/unit/config_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ConfigTest < Test::Unit::TestCase 4 | def test_set_ssl_cert_file 5 | @config = PaypalAdaptive::Config.new("test", { "ssl_cert_file" => "/path/to/cacert.pem" }) 6 | assert_equal "/path/to/cacert.pem", @config.ssl_cert_file 7 | assert_equal nil, @config.ssl_cert_path 8 | end 9 | 10 | def test_blank_ssl_cert_file 11 | @config = PaypalAdaptive::Config.new("test", { "ssl_cert_file" => "" }) 12 | assert_equal nil, @config.ssl_cert_file 13 | assert_equal nil, @config.ssl_cert_path 14 | end 15 | 16 | def test_erb_tags 17 | ENV['paypal.username'] = 'account@email.com' 18 | ENV['paypal.password'] = 's3krit' 19 | 20 | config = PaypalAdaptive::Config.new("with_erb_tags") 21 | assert_equal 'account@email.com', config.headers["X-PAYPAL-SECURITY-USERID"] 22 | assert_equal 's3krit', config.headers["X-PAYPAL-SECURITY-PASSWORD"] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/unit/get_shipping_addresses_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GetShippingAddressesTest < Test::Unit::TestCase 4 | def setup 5 | @pay_key = nil 6 | @pay_request = PaypalAdaptive::Request.new("test") 7 | end 8 | 9 | def test_get_shipping_addresses 10 | puts "-------" 11 | puts "get shipping addresses" 12 | 13 | # /Pay 14 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_simple_pay_request_1.json") 15 | 16 | data = read_json_file(data_filepath) 17 | data["actionType"] = "CREATE" 18 | 19 | pp_response = @pay_request.pay(data) 20 | @pay_key = pp_response['payKey'] 21 | 22 | unless pp_response.success? 23 | puts pp_response.errors 24 | end 25 | 26 | assert pp_response.success? 27 | 28 | # /SetPaymentOptions 29 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_set_payment_options_request.json") 30 | 31 | data = read_json_file(data_filepath) 32 | data["payKey"] = @pay_key 33 | 34 | pp_response = @pay_request.set_payment_options(data) 35 | 36 | unless pp_response.success? 37 | puts pp_response.errors 38 | end 39 | 40 | assert pp_response.success? 41 | 42 | # /GetShippingAddresses 43 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_get_shipping_addresses_request.json") 44 | 45 | data = read_json_file(data_filepath) 46 | data["key"] = @pay_key 47 | 48 | pp_response = @pay_request.get_shipping_addresses(data) 49 | 50 | unless pp_response.success? 51 | puts pp_response.errors 52 | end 53 | 54 | assert pp_response.success? 55 | end 56 | 57 | def read_json_file(filepath) 58 | File.open(filepath, "rb"){|f| JSON.parse(f.read)} 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/unit/get_verified_status_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GetVerifiedStatusTest < Test::Unit::TestCase 4 | def setup 5 | @get_verified_status_request = PaypalAdaptive::Request.new("test") 6 | end 7 | 8 | def test_get_verified_status 9 | puts "-------" 10 | puts "get_verified_status" 11 | 12 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","verified_get_verified_status_request.json") 13 | 14 | data = read_json_file(data_filepath) 15 | pp_response = @get_verified_status_request.get_verified_status(data) 16 | 17 | puts "account status: #{pp_response['accountStatus']}" 18 | assert_equal true, pp_response.success? 19 | end 20 | 21 | def read_json_file(filepath) 22 | File.open(filepath, "rb"){|f| JSON.parse(f.read)} 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/unit/pay_request_schema_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PayRequestSchemaTest < Test::Unit::TestCase 4 | def setup 5 | @schema_filepath = File.join(File.dirname(__FILE__),"..", "..", "lib","pay_request_schema.json") 6 | @schema = File.open(@schema_filepath, "rb"){|f| JSON.parse(f.read)} 7 | end 8 | 9 | def test_valid_simple_pay 10 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_simple_pay_request_1.json") 11 | data = read_json_file(data_filepath) 12 | 13 | #receiverList not validating correctly, is it due to the schema or jsonschema parsing? 14 | assert_nothing_raised do 15 | JSON::Schema.validate(data, @schema) 16 | end 17 | end 18 | 19 | def test_invalid_simple_pay 20 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","invalid_simple_pay_request_1.json") 21 | data = read_json_file(data_filepath) 22 | 23 | assert_raise JSON::Schema::ValueError do 24 | JSON::Schema.validate(data, @schema) 25 | end 26 | end 27 | 28 | def test_valid_chain_pay 29 | #TODO 30 | end 31 | 32 | def test_invalid_chain_pay 33 | #TODO 34 | end 35 | 36 | def test_valid_parallel_pay 37 | #TODO 38 | end 39 | 40 | def test_invalid_parallel_pay 41 | #TODO 42 | end 43 | 44 | def read_json_file(filepath) 45 | File.open(filepath, "rb"){|f| JSON.parse(f.read)} 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/unit/pay_request_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PayRequestTest < Test::Unit::TestCase 4 | def setup 5 | @pay_request = PaypalAdaptive::Request.new("test") 6 | end 7 | 8 | def test_valid_simple_pay 9 | data = read_json_file("valid_simple_pay_request_1.json") 10 | assert_success_response @pay_request.pay(data) 11 | end 12 | 13 | def test_invalid_simple_pay 14 | data = read_json_file("invalid_simple_pay_request_1.json") 15 | assert_error_response "580022", @pay_request.pay(data) 16 | end 17 | 18 | def test_valid_chain_pay 19 | data = read_json_file("valid_chain_pay_request.json") 20 | assert_success_response @pay_request.pay(data) 21 | end 22 | 23 | def test_invalid_chain_pay 24 | data = read_json_file("invalid_chain_pay_request.json") 25 | assert_error_response "579017", @pay_request.pay(data) 26 | end 27 | 28 | def test_valid_parallel_pay 29 | data = read_json_file("valid_parallel_pay_request.json") 30 | assert_success_response @pay_request.pay(data) 31 | end 32 | 33 | def test_invalid_parallel_pay 34 | data = read_json_file("invalid_parallel_pay_request.json") 35 | assert_error_response "579040", @pay_request.pay(data) 36 | end 37 | 38 | def test_preapproval 39 | #TODO 40 | end 41 | 42 | def test_preapproval_details 43 | #TODO 44 | end 45 | 46 | def test_cancel_preapproval 47 | #TODO 48 | end 49 | 50 | def test_convert_currency 51 | #TODO 52 | end 53 | 54 | def test_refund 55 | #TODO 56 | end 57 | 58 | def read_json_file(filename) 59 | JSON.parse(File.read(File.join(File.dirname(__FILE__),"..","data",filename))) 60 | end 61 | 62 | APPROVE_URL_PATTERN = %r{^https://www.sandbox.paypal.com/webscr\?cmd=_ap-payment&paykey=AP-} 63 | APPROVE_URL_PATTERN_JP = %r{^https://www.sandbox.paypal.com/jp/webscr\?cmd=_ap-payment&paykey=AP-} 64 | MINI_APPROVE_URL_PATTERN = %r{^https://www.sandbox.paypal.com/webapps/adaptivepayment/flow/pay\?expType=mini&paykey=AP-} 65 | 66 | def assert_success_response(pp_response) 67 | assert_equal true, pp_response.success?, "expected success: #{pp_response.inspect}" 68 | assert_match APPROVE_URL_PATTERN, pp_response.approve_paypal_payment_url 69 | assert_match APPROVE_URL_PATTERN_JP, pp_response.approve_paypal_payment_url(:country => :jp) 70 | assert_match MINI_APPROVE_URL_PATTERN, pp_response.approve_paypal_payment_url('mini') 71 | assert_match MINI_APPROVE_URL_PATTERN, pp_response.approve_paypal_payment_url(:type => 'mini') 72 | end 73 | 74 | def assert_error_response(error_code, pp_response) 75 | assert_equal false, pp_response.success? 76 | pp_errors = pp_response.errors.first 77 | assert_equal "Error", pp_errors["severity"] 78 | assert_equal error_code, pp_errors["errorId"] 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/unit/payment_details_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PaymentDetailsTest < Test::Unit::TestCase 4 | def setup 5 | @pay_key = nil 6 | @pay_request = PaypalAdaptive::Request.new("test") 7 | end 8 | 9 | def test_payment_details 10 | puts "-------" 11 | puts "payment details" 12 | 13 | # /Pay 14 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_chain_pay_request.json") 15 | 16 | data = read_json_file(data_filepath) 17 | 18 | pp_response = @pay_request.pay(data) 19 | @pay_key = pp_response['payKey'] 20 | 21 | unless pp_response.success? 22 | puts pp_response.errors 23 | end 24 | 25 | assert pp_response.success? 26 | 27 | # /PaymentDetails 28 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_payment_details_request.json") 29 | 30 | data = read_json_file(data_filepath) 31 | data["payKey"] = @pay_key 32 | 33 | pp_response = @pay_request.payment_details(data) 34 | 35 | unless pp_response.success? 36 | puts pp_response.errors 37 | end 38 | 39 | assert pp_response.success? 40 | 41 | assert_equal 'CREATED', pp_response['status'] 42 | end 43 | 44 | def read_json_file(filepath) 45 | File.open(filepath, "rb"){|f| JSON.parse(f.read)} 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/unit/payment_options_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PaymentOptionsTest < Test::Unit::TestCase 4 | def setup 5 | @pay_key = nil 6 | @pay_request = PaypalAdaptive::Request.new("test") 7 | end 8 | 9 | def test_payment_options 10 | puts "-------" 11 | puts "set payment options" 12 | 13 | # /Pay 14 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_simple_pay_request_1.json") 15 | 16 | data = read_json_file(data_filepath) 17 | data["actionType"] = "CREATE" 18 | 19 | pp_response = @pay_request.pay(data) 20 | @pay_key = pp_response['payKey'] 21 | 22 | unless pp_response.success? 23 | puts pp_response.errors 24 | end 25 | 26 | assert pp_response.success? 27 | 28 | # /SetPaymentOptions 29 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_set_payment_options_request.json") 30 | 31 | data = read_json_file(data_filepath) 32 | data["payKey"] = @pay_key 33 | 34 | pp_response = @pay_request.set_payment_options(data) 35 | 36 | unless pp_response.success? 37 | puts pp_response.errors 38 | end 39 | 40 | assert pp_response.success? 41 | 42 | puts "-------" 43 | puts "get payment options" 44 | 45 | # /GetPaymentOptions 46 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_get_payment_options_request.json") 47 | 48 | data = read_json_file(data_filepath) 49 | data["payKey"] = @pay_key 50 | 51 | pp_response = @pay_request.get_payment_options(data) 52 | 53 | unless pp_response.success? 54 | puts pp_response.errors 55 | end 56 | 57 | assert pp_response.success? 58 | 59 | assert_equal pp_response["senderOptions"]["requireShippingAddressSelection"], "true" 60 | assert_equal pp_response["receiverOptions"].first["invoiceData"]["item"].first["identifier"], "1" 61 | assert_equal pp_response["receiverOptions"].first["invoiceData"]["item"].first["name"], "Sample Product" 62 | assert_equal pp_response["receiverOptions"].first["invoiceData"]["item"].first["itemCount"], "1" 63 | assert_equal pp_response["receiverOptions"].first["invoiceData"]["item"].first["itemPrice"], "10.00" 64 | assert_equal pp_response["receiverOptions"].first["invoiceData"]["item"].first["price"], "10.00" 65 | assert_equal pp_response["receiverOptions"].first["invoiceData"]["totalShipping"], "0.00" 66 | end 67 | 68 | def read_json_file(filepath) 69 | File.open(filepath, "rb"){|f| JSON.parse(f.read)} 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/unit/preapproval_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PreapprovalTest < Test::Unit::TestCase 4 | def setup 5 | @preapproval_request = PaypalAdaptive::Request.new("test") 6 | end 7 | 8 | def test_preapproval 9 | puts "-------" 10 | puts "valid test" 11 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","valid_preapproval.json") 12 | 13 | data = read_json_file(data_filepath) 14 | p data 15 | 16 | pp_response = @preapproval_request.preapproval(data) 17 | puts "preapproval code is #{pp_response['preapprovalKey']}" 18 | 19 | assert pp_response.success? 20 | assert_not_nil pp_response.preapproval_paypal_payment_url 21 | assert_not_nil pp_response['preapprovalKey'] 22 | end 23 | 24 | 25 | def test_invalid_preapproval 26 | puts "-------" 27 | puts "invalid" 28 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","invalid_preapproval.json") 29 | 30 | data = read_json_file(data_filepath) 31 | pp_response = @preapproval_request.preapproval(data) 32 | puts "error message is #{pp_response.error_message}" 33 | 34 | assert pp_response.success? == false 35 | assert_nil pp_response.preapproval_paypal_payment_url 36 | assert_nil pp_response['preapprovalKey'] 37 | end 38 | 39 | def test_invalid_preapproval_with_bad_credit_information 40 | puts "-------" 41 | puts "invalid" 42 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","invalid_preapproval_error_execstatus.json") 43 | 44 | data = read_json_file(data_filepath) 45 | pp_response = @preapproval_request.preapproval(data) 46 | puts "error message is #{pp_response.error_message}" 47 | 48 | assert pp_response.success? == false 49 | end 50 | 51 | def test_erred_preapproval_payment_message 52 | puts "-------" 53 | puts "invalid" 54 | data_filepath = File.join(File.dirname(__FILE__),"..", "data","invalid_preapproval_payment.json") 55 | 56 | data = read_json_file(data_filepath) 57 | pp_response = @preapproval_request.preapproval(data) 58 | puts "error message is #{pp_response.error_message}" 59 | 60 | assert pp_response.error_message == "This transaction cannot be processed. Please enter a valid credit card number and type" 61 | end 62 | 63 | def read_json_file(filepath) 64 | File.open(filepath, "rb"){|f| JSON.parse(f.read)} 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /test/unit/request_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RequestTest < Test::Unit::TestCase 4 | def setup 5 | @schema_filepath = File.join(File.dirname(__FILE__),"..", "..", "lib","pay_request_schema.json") 6 | @schema = File.open(@schema_filepath, "rb"){|f| JSON.parse(f.read)} 7 | end 8 | 9 | def test_post_should_return_hash 10 | request = PaypalAdaptive::Request.new('test') 11 | response = request.post({:data => true}, '/AdaptivePayments/Pay') 12 | assert_instance_of Hash, response 13 | end 14 | 15 | def test_post_should_return_error_message_when_request_is_invalid 16 | request = PaypalAdaptive::Request.new('test') 17 | response = request.post({:data => true}, '/some-random-url') 18 | assert_instance_of Hash, response 19 | assert_equal "Connection Reset. Request invalid URL.", response["error"][0]["message"] 20 | end 21 | 22 | def test_post_should_return_error_details_when_response_is_invalid 23 | WebMock.disable_net_connect! 24 | stub_request(:post, "https://svcs.sandbox.paypal.com/some-random-url"). 25 | to_return(:status => [500, "Internal Server Error"], :body => "Something went wrong") 26 | request = PaypalAdaptive::Request.new('test') 27 | response = request.post({:data => true}, '/some-random-url') 28 | assert_instance_of Hash, response 29 | error = response["error"].first 30 | assert_instance_of Hash, error 31 | assert_equal "Response is not in JSON format.", error["message"] 32 | assert_equal "500", error["details"]["httpCode"] 33 | assert_equal "Internal Server Error", error["details"]["httpMessage"] 34 | assert_equal "Something went wrong", error["details"]["httpBody"] 35 | ensure 36 | WebMock.allow_net_connect! 37 | end 38 | end 39 | --------------------------------------------------------------------------------