paypal-express 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +17 -0
  7. data/Rakefile +20 -0
  8. data/VERSION +1 -0
  9. data/lib/cert +3509 -0
  10. data/lib/paypal.rb +56 -0
  11. data/lib/paypal/base.rb +19 -0
  12. data/lib/paypal/exceptions.rb +21 -0
  13. data/lib/paypal/express.rb +1 -0
  14. data/lib/paypal/express/request.rb +64 -0
  15. data/lib/paypal/express/response.rb +19 -0
  16. data/lib/paypal/nvp/request.rb +59 -0
  17. data/lib/paypal/nvp/response.rb +145 -0
  18. data/lib/paypal/payment/recurring.rb +43 -0
  19. data/lib/paypal/payment/recurring/activation.rb +14 -0
  20. data/lib/paypal/payment/recurring/billing.rb +37 -0
  21. data/lib/paypal/payment/recurring/summary.rb +11 -0
  22. data/lib/paypal/payment/request.rb +24 -0
  23. data/lib/paypal/payment/response.rb +36 -0
  24. data/lib/paypal/payment/response/amount.rb +11 -0
  25. data/lib/paypal/payment/response/info.rb +40 -0
  26. data/lib/paypal/payment/response/payer.rb +7 -0
  27. data/lib/paypal/payment/response/ship_to.rb +7 -0
  28. data/lib/paypal/util.rb +28 -0
  29. data/lib/restclient_with_ssl_support.rb +16 -0
  30. data/paypal-express.gemspec +24 -0
  31. data/spec/fake_response/CreateRecurringPaymentsProfile/failure.txt +1 -0
  32. data/spec/fake_response/CreateRecurringPaymentsProfile/success.txt +1 -0
  33. data/spec/fake_response/DoExpressCheckoutPayment/failure.txt +1 -0
  34. data/spec/fake_response/DoExpressCheckoutPayment/success.txt +1 -0
  35. data/spec/fake_response/GetExpressCheckoutDetails/failure.txt +1 -0
  36. data/spec/fake_response/GetExpressCheckoutDetails/success.txt +1 -0
  37. data/spec/fake_response/GetRecurringPaymentsProfileDetails/failure.txt +1 -0
  38. data/spec/fake_response/GetRecurringPaymentsProfileDetails/success.txt +1 -0
  39. data/spec/fake_response/ManageRecurringPaymentsProfileStatus/failure.txt +1 -0
  40. data/spec/fake_response/ManageRecurringPaymentsProfileStatus/success.txt +1 -0
  41. data/spec/fake_response/SetExpressCheckout/failure.txt +1 -0
  42. data/spec/fake_response/SetExpressCheckout/success.txt +1 -0
  43. data/spec/helpers/fake_response_helper.rb +27 -0
  44. data/spec/paypal/exception_spec.rb +17 -0
  45. data/spec/paypal/express/request_spec.rb +223 -0
  46. data/spec/paypal/express/response_spec.rb +33 -0
  47. data/spec/paypal/nvp/request_spec.rb +88 -0
  48. data/spec/paypal/payment/recurring_spec.rb +114 -0
  49. data/spec/paypal/payment/request_spec.rb +55 -0
  50. data/spec/paypal/payment/response/amount_spec.rb +36 -0
  51. data/spec/paypal/payment/response/info_spec.rb +88 -0
  52. data/spec/paypal/payment/response/payer_spec.rb +26 -0
  53. data/spec/paypal/payment/response/ship_to_spec.rb +26 -0
  54. data/spec/paypal/util_spec.rb +32 -0
  55. data/spec/spec_helper.rb +22 -0
  56. metadata +267 -0
data/lib/paypal.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'logger'
2
+ require 'active_support/core_ext'
3
+ require 'attr_required'
4
+ require 'attr_optional'
5
+ require 'restclient_with_ssl_support'
6
+
7
+ module Paypal
8
+
9
+ API_VERSION = '66.0'
10
+ ENDPOINT = {
11
+ :production => 'https://www.paypal.com/cgi-bin/webscr',
12
+ :sandbox => 'https://www.sandbox.paypal.com/cgi-bin/webscr'
13
+ }
14
+
15
+ def self.log(message, mode = :info)
16
+ self.logger.send mode, message
17
+ end
18
+ def self.logger
19
+ @@logger
20
+ end
21
+ def self.logger=(logger)
22
+ @@logger = logger
23
+ end
24
+ @@logger = Logger.new(STDERR)
25
+ @@logger.progname = 'Paypal::Express'
26
+
27
+ def self.sandbox?
28
+ @@sandbox
29
+ end
30
+ def self.sandbox!
31
+ self.sandbox = true
32
+ end
33
+ def self.sandbox=(boolean)
34
+ @@sandbox = boolean
35
+ end
36
+ self.sandbox = false
37
+
38
+ end
39
+
40
+ require 'paypal/util'
41
+ require 'paypal/exceptions'
42
+ require 'paypal/base'
43
+ require 'paypal/nvp/request'
44
+ require 'paypal/nvp/response'
45
+ require 'paypal/express/request'
46
+ require 'paypal/express/response'
47
+ require 'paypal/payment/request'
48
+ require 'paypal/payment/response'
49
+ require 'paypal/payment/response/amount'
50
+ require 'paypal/payment/response/info'
51
+ require 'paypal/payment/response/payer'
52
+ require 'paypal/payment/response/ship_to'
53
+ require 'paypal/payment/recurring'
54
+ require 'paypal/payment/recurring/activation'
55
+ require 'paypal/payment/recurring/billing'
56
+ require 'paypal/payment/recurring/summary'
@@ -0,0 +1,19 @@
1
+ module Paypal
2
+ class Base
3
+ include AttrRequired, AttrOptional, Util
4
+
5
+ def initialize(attributes = {})
6
+ if attributes.is_a?(Hash)
7
+ (required_attributes + optional_attributes).each do |key|
8
+ value = if numeric_attribute?(key)
9
+ Util.to_numeric(attributes[key])
10
+ else
11
+ attributes[key]
12
+ end
13
+ self.send "#{key}=", value
14
+ end
15
+ end
16
+ attr_missing!
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Paypal
2
+
3
+ class Exception < StandardError; end
4
+
5
+ class HttpError < Exception
6
+ attr_accessor :code, :message, :body
7
+ def initialize(code, message, body = '')
8
+ @code = code
9
+ @message = message
10
+ @body = body
11
+ end
12
+ end
13
+
14
+ class APIError < Exception
15
+ attr_accessor :response
16
+ def initialize(response = {})
17
+ @response = response
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1 @@
1
+ require 'paypal'
@@ -0,0 +1,64 @@
1
+ module Paypal
2
+ module Express
3
+ class Request < NVP::Request
4
+ attr_required :return_url, :cancel_url
5
+
6
+ def setup(payment_requests)
7
+ params = {
8
+ :RETURNURL => self.return_url,
9
+ :CANCELURL => self.cancel_url
10
+ }
11
+ Array(payment_requests).each_with_index do |payment_request, index|
12
+ params.merge! payment_request.to_params(index)
13
+ end
14
+ response = self.request :SetExpressCheckout, params
15
+ Response.new response
16
+ end
17
+
18
+ def details(token)
19
+ response = self.request :GetExpressCheckoutDetails, {:TOKEN => token}
20
+ Response.new response
21
+ end
22
+
23
+ def checkout!(token, payer_id, payment_requests)
24
+ params = {
25
+ :TOKEN => token,
26
+ :PAYERID => payer_id
27
+ }
28
+ Array(payment_requests).each_with_index do |payment_request, index|
29
+ params.merge! payment_request.to_params(index)
30
+ end
31
+ response = self.request :DoExpressCheckoutPayment, params
32
+ Response.new response
33
+ end
34
+
35
+ def subscribe!(token, recurring_profile)
36
+ params = {
37
+ :TOKEN => token
38
+ }
39
+ params.merge! recurring_profile.to_params
40
+ response = self.request :CreateRecurringPaymentsProfile, params
41
+ Response.new response
42
+ end
43
+
44
+ def subscription(profile_id)
45
+ params = {
46
+ :PROFILEID => profile_id
47
+ }
48
+ response = self.request :GetRecurringPaymentsProfileDetails, params
49
+ Response.new response
50
+ end
51
+
52
+ def renew!(profile_id, action, note = '')
53
+ params = {
54
+ :PROFILEID => profile_id,
55
+ :ACTION => action,
56
+ :NOTE => note
57
+ }
58
+ response = self.request :ManageRecurringPaymentsProfileStatus, params
59
+ Response.new response
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ module Paypal
2
+ module Express
3
+ class Response < NVP::Response
4
+ def redirect_uri
5
+ endpoint = if Paypal.sandbox?
6
+ Paypal::ENDPOINT[:sandbox]
7
+ else
8
+ Paypal::ENDPOINT[:production]
9
+ end
10
+ endpoint = URI.parse endpoint
11
+ endpoint.query = {
12
+ :cmd => '_express-checkout',
13
+ :token => self.token
14
+ }.to_query
15
+ endpoint.to_s
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ module Paypal
2
+ module NVP
3
+ class Request < Base
4
+ attr_required :username, :password, :signature
5
+ attr_accessor :version, :endpoint
6
+
7
+ ENDPOINT = {
8
+ :production => 'https://api-3t.paypal.com/nvp',
9
+ :sandbox => 'https://api-3t.sandbox.paypal.com/nvp'
10
+ }
11
+
12
+ def initialize(attributes = {})
13
+ @version = API_VERSION
14
+ @endpoint = if Paypal.sandbox?
15
+ ENDPOINT[:sandbox]
16
+ else
17
+ ENDPOINT[:production]
18
+ end
19
+ super
20
+ end
21
+
22
+ def common_params
23
+ {
24
+ :USER => self.username,
25
+ :PWD => self.password,
26
+ :SIGNATURE => self.signature,
27
+ :VERSION => self.version
28
+ }
29
+ end
30
+
31
+ def request(method, params = {})
32
+ handle_response do
33
+ post(method, params)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def post(method, params)
40
+ RestClient.post(self.endpoint, common_params.merge(params).merge(:METHOD => method))
41
+ end
42
+
43
+ def handle_response
44
+ response = yield
45
+ response = CGI.parse(response).inject({}) do |res, (k, v)|
46
+ res.merge!(k.to_sym => v.first)
47
+ end
48
+ case response[:ACK]
49
+ when 'Success', 'SuccessWithWarning'
50
+ response
51
+ else
52
+ raise APIError.new(response)
53
+ end
54
+ rescue RestClient::Exception => e
55
+ raise HttpError.new(e.http_code, e.message, e.http_body)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,145 @@
1
+ module Paypal
2
+ module NVP
3
+ class Response < Base
4
+ cattr_reader :attribute_mapping
5
+ @@attribute_mapping = {
6
+ :ACK => :ack,
7
+ :ADDRESSSTATUS => :address_status,
8
+ :BILLINGAGREEMENTACCEPTEDSTATUS => :billing_agreement_accepted_status,
9
+ :BUILD => :build,
10
+ :CHECKOUTSTATUS => :checkout_status,
11
+ :CORRELATIONID => :colleration_id,
12
+ :COUNTRYCODE => :country_code,
13
+ :CURRENCYCODE => :currency_code,
14
+ :TIMESTAMP => :timestamp,
15
+ :TOKEN => :token,
16
+ :VERSION => :version
17
+ }
18
+ attr_accessor *@@attribute_mapping.values
19
+ attr_accessor :shipping_options_is_default, :success_page_redirect_requested, :insurance_option_selected
20
+ attr_accessor :amount, :ship_to, :payer, :recurring, :payment_responses, :payment_info
21
+
22
+ def initialize(attributes = {})
23
+ attrs = attributes.dup
24
+ @@attribute_mapping.each do |key, value|
25
+ self.send "#{value}=", attrs.delete(key)
26
+ end
27
+ @shipping_options_is_default = attrs.delete(:SHIPPINGOPTIONISDEFAULT) == 'true'
28
+ @success_page_redirect_requested = attrs.delete(:SUCCESSPAGEREDIRECTREQUESTED) == 'true'
29
+ @insurance_option_selected = attrs.delete(:INSURANCEOPTIONSELECTED) == 'true'
30
+ @amount = Payment::Response::Amount.new(
31
+ :total => attrs.delete(:AMT),
32
+ :handing => attrs.delete(:HANDLINGAMT),
33
+ :insurance => attrs.delete(:INSURANCEAMT),
34
+ :ship_disc => attrs.delete(:SHIPDISCAMT),
35
+ :shipping => attrs.delete(:SHIPPINGAMT),
36
+ :tax => attrs.delete(:TAXAMT)
37
+ )
38
+ @ship_to = Payment::Response::ShipTo.new(
39
+ :owner => attrs.delete(:SHIPADDRESSOWNER),
40
+ :status => attrs.delete(:SHIPADDRESSSTATUS),
41
+ :name => attrs.delete(:SHIPTONAME),
42
+ :zip => attrs.delete(:SHIPTOZIP),
43
+ :street => attrs.delete(:SHIPTOSTREET),
44
+ :city => attrs.delete(:SHIPTOCITY),
45
+ :state => attrs.delete(:SHIPTOSTATE),
46
+ :country_code => attrs.delete(:SHIPTOCOUNTRYCODE),
47
+ :country_name => attrs.delete(:SHIPTOCOUNTRYNAME)
48
+ )
49
+ if attrs[:PAYERID]
50
+ @payer = Payment::Response::Payer.new(
51
+ :identifier => attrs.delete(:PAYERID),
52
+ :status => attrs.delete(:PAYERSTATUS),
53
+ :first_name => attrs.delete(:FIRSTNAME),
54
+ :last_name => attrs.delete(:LASTNAME),
55
+ :email => attrs.delete(:EMAIL)
56
+ )
57
+ end
58
+ if attrs[:PROFILEID]
59
+ @recurring = Payment::Recurring.new(
60
+ :identifier => attrs.delete(:PROFILEID),
61
+ # NOTE:
62
+ # CreateRecurringPaymentsProfile returns PROFILESTATUS
63
+ # GetRecurringPaymentsProfileDetails returns STATUS
64
+ :description => attrs.delete(:DESC),
65
+ :status => attrs.delete(:STATUS) || attrs.delete(:PROFILESTATUS),
66
+ :start_date => attrs.delete(:PROFILESTARTDATE),
67
+ :name => attrs.delete(:SUBSCRIBERNAME),
68
+ :reference => attrs.delete(:PROFILEREFERENCE),
69
+ :auto_bill => attrs.delete(:AUTOBILLOUTAMT),
70
+ :max_fails => attrs.delete(:MAXFAILEDPAYMENTS),
71
+ :aggregate_amount => attrs.delete(:AGGREGATEAMT),
72
+ :aggregate_optional_amount => attrs.delete(:AGGREGATEOPTIONALAMT),
73
+ :final_payment_date => attrs.delete(:FINALPAYMENTDUEDATE)
74
+ )
75
+ if attrs[:BILLINGPERIOD]
76
+ @recurring.billing = Payment::Recurring::Billing.new(
77
+ :amount => @amount,
78
+ :currency_code => @currency_code,
79
+ :period => attrs.delete(:BILLINGPERIOD),
80
+ :frequency => attrs.delete(:BILLINGFREQUENCY),
81
+ :total_cycles => attrs.delete(:TOTALBILLINGCYCLES),
82
+ :trial_amount_paid => attrs.delete(:TRIALAMTPAID)
83
+ )
84
+ end
85
+ if attrs[:REGULARAMT]
86
+ @recurring.regular_billing = Payment::Recurring::Billing.new(
87
+ :amount => attrs.delete(:REGULARAMT),
88
+ :shipping_amount => attrs.delete(:REGULARSHIPPINGAMT),
89
+ :tax_amount => attrs.delete(:REGULARTAXAMT),
90
+ :currency_code => attrs.delete(:REGULARCURRENCYCODE),
91
+ :period => attrs.delete(:REGULARBILLINGPERIOD),
92
+ :frequency => attrs.delete(:REGULARBILLINGFREQUENCY),
93
+ :total_cycles => attrs.delete(:REGULARTOTALBILLINGCYCLES),
94
+ :paid => attrs.delete(:REGULARAMTPAID)
95
+ )
96
+ @recurring.summary = Payment::Recurring::Summary.new(
97
+ :next_billing_date => attrs.delete(:NEXTBILLINGDATE),
98
+ :cycles_completed => attrs.delete(:NUMCYCLESCOMPLETED),
99
+ :cycles_remaining => attrs.delete(:NUMCYCLESREMAINING),
100
+ :outstanding_balance => attrs.delete(:OUTSTANDINGBALANCE),
101
+ :failed_count => attrs.delete(:FAILEDPAYMENTCOUNT),
102
+ :last_payment_date => attrs.delete(:LASTPAYMENTDATE),
103
+ :last_payment_amount => attrs.delete(:LASTPAYMENTAMT)
104
+ )
105
+ end
106
+ end
107
+
108
+ # payment_responses
109
+ payment_responses = []
110
+ attrs.keys.each do |attribute|
111
+ prefix, index, key = attribute.to_s.split('_')
112
+ case prefix
113
+ when 'PAYMENTREQUEST', 'PAYMENTREQUESTINFO'
114
+ payment_responses[index.to_i] ||= {}
115
+ payment_responses[index.to_i][key.to_sym] = attrs.delete(attribute)
116
+ end
117
+ end
118
+ @payment_responses = payment_responses.collect do |_attrs_|
119
+ Payment::Response.new _attrs_
120
+ end
121
+
122
+ # payment_info
123
+ payment_info = []
124
+ attrs.keys.each do |_attr_|
125
+ prefix, index, key = _attr_.to_s.split('_')
126
+ if prefix == 'PAYMENTINFO'
127
+ payment_info[index.to_i] ||= {}
128
+ payment_info[index.to_i][key.to_sym] = attrs.delete(_attr_)
129
+ end
130
+ end
131
+ @payment_info = payment_info.collect do |_attrs_|
132
+ Payment::Response::Info.new _attrs_
133
+ end
134
+
135
+ # remove duplicated parameters
136
+ attrs.delete(:SHIPTOCOUNTRY) # NOTE: Same with SHIPTOCOUNTRYCODE
137
+
138
+ # warn ignored attrs
139
+ attrs.each do |key, value|
140
+ Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,43 @@
1
+ module Paypal
2
+ module Payment
3
+ class Recurring < Base
4
+ attr_optional :start_date, :description, :identifier, :status, :name, :reference, :max_fails, :auto_bill, :aggregate_amount, :aggregate_optional_amount, :final_payment_date
5
+ attr_accessor :activation, :billing, :regular_billing, :summary
6
+
7
+ def initialize(attributes = {})
8
+ super
9
+ @activation = Activation.new attributes[:activation] if attributes[:activation]
10
+ @billing = Billing.new attributes[:billing] if attributes[:billing]
11
+ @regular_billing = Billing.new attributes[:regular_billing] if attributes[:regular_billing]
12
+ @summary = Summary.new attributes[:summary] if attributes[:summary]
13
+ end
14
+
15
+ def to_params
16
+ params = [
17
+ self.billing,
18
+ self.activation
19
+ ].compact.inject({}) do |params, attribute|
20
+ params.merge! attribute.to_params
21
+ end
22
+ if self.start_date.is_a?(Time)
23
+ self.start_date = self.start_date.to_s(:db)
24
+ end
25
+ params.merge!(
26
+ :DESC => self.description,
27
+ :MAXFAILEDPAYMENTS => self.max_fails,
28
+ :AUTOBILLAMT => self.auto_bill,
29
+ :PROFILESTARTDATE => self.start_date,
30
+ :SUBSCRIBERNAME => self.name,
31
+ :PROFILEREFERENCE => self.reference
32
+ )
33
+ params.delete_if do |k, v|
34
+ v.blank?
35
+ end
36
+ end
37
+
38
+ def numeric_attribute?(key)
39
+ super || [:max_fails, :failed_count].include?(key)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,14 @@
1
+ module Paypal
2
+ module Payment
3
+ class Recurring::Activation < Base
4
+ attr_optional :initial_amount, :failed_action
5
+
6
+ def to_params
7
+ {
8
+ :INITAMT => Util.formatted_amount(self.initial_amount),
9
+ :FAILEDINITAMTACTION => self.failed_action
10
+ }
11
+ end
12
+ end
13
+ end
14
+ end