express-checkout 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ module Express
2
+ class Exception
3
+ class HttpError < Exception
4
+ attr_accessor :code, :message, :body
5
+ def initialize(code, message, body = '')
6
+ @code = code
7
+ @message = message
8
+ @body = body
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Express
2
+ module IPN
3
+ def self.endpoint
4
+ _endpoint_ = URI.parse Paypal.endpoint
5
+ _endpoint_.query = {
6
+ cmd: '_notify-validate'
7
+ }.to_query
8
+ _endpoint_.to_s
9
+ end
10
+
11
+ def self.verify!(raw_post)
12
+ response = RestClient.post(
13
+ endpoint, raw_post
14
+ )
15
+ case response.body
16
+ when 'VERIFIED'
17
+ true
18
+ else
19
+ raise Exception::APIError.new(response.body)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,64 @@
1
+ module Express
2
+ module NVP
3
+ class Request < Base
4
+ attr_required :username, :password, :signature
5
+ attr_optional :subject
6
+ attr_accessor :version
7
+
8
+ ENDPOINT = {
9
+ :production => 'https://api-3t.paypal.com/nvp',
10
+ :sandbox => 'https://api-3t.sandbox.paypal.com/nvp'
11
+ }
12
+
13
+ def self.endpoint
14
+ if Paypal.sandbox?
15
+ ENDPOINT[:sandbox]
16
+ else
17
+ ENDPOINT[:production]
18
+ end
19
+ end
20
+
21
+ def initialize(attributes = {})
22
+ @version = Paypal.api_version
23
+ super
24
+ end
25
+
26
+ def common_params
27
+ {
28
+ :USER => self.username,
29
+ :PWD => self.password,
30
+ :SIGNATURE => self.signature,
31
+ :SUBJECT => self.subject,
32
+ :VERSION => self.version
33
+ }
34
+ end
35
+
36
+ def request(method, params = {})
37
+ handle_response do
38
+ post(method, params)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def post(method, params)
45
+ RestClient.post(self.class.endpoint, common_params.merge(params).merge(:METHOD => method))
46
+ end
47
+
48
+ def handle_response
49
+ response = yield
50
+ response = CGI.parse(response).inject({}) do |res, (k, v)|
51
+ res.merge!(k.to_sym => v.first)
52
+ end
53
+ case response[:ACK]
54
+ when 'Success', 'SuccessWithWarning'
55
+ response
56
+ else
57
+ raise Exception::APIError.new(response)
58
+ end
59
+ rescue RestClient::Exception => e
60
+ raise Exception::HttpError.new(e.http_code, e.message, e.http_body)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,233 @@
1
+ module Express
2
+ module NVP
3
+ class Response < Base
4
+ cattr_reader :attribute_mapping
5
+ @@attribute_mapping = {
6
+ :ACK => :ack,
7
+ :BUILD => :build,
8
+ :BILLINGAGREEMENTACCEPTEDSTATUS => :billing_agreement_accepted_status,
9
+ :CHECKOUTSTATUS => :checkout_status,
10
+ :CORRELATIONID => :correlation_id,
11
+ :COUNTRYCODE => :country_code,
12
+ :CURRENCYCODE => :currency_code,
13
+ :DESC => :description,
14
+ :NOTIFYURL => :notify_url,
15
+ :TIMESTAMP => :timestamp,
16
+ :TOKEN => :token,
17
+ :VERSION => :version,
18
+ # Some of the attributes below are duplicates of what
19
+ # exists in the payment response, but paypal doesn't
20
+ # prefix these with PAYMENTREQUEST when issuing a
21
+ # GetTransactionDetails response.
22
+ :RECEIVEREMAIL => :receiver_email,
23
+ :RECEIVERID => :receiver_id,
24
+ :SUBJECT => :subject,
25
+ :TRANSACTIONID => :transaction_id,
26
+ :TRANSACTIONTYPE => :transaction_type,
27
+ :PAYMENTTYPE => :payment_type,
28
+ :ORDERTIME => :order_time,
29
+ :PAYMENTSTATUS => :payment_status,
30
+ :PENDINGREASON => :pending_reason,
31
+ :REASONCODE => :reason_code,
32
+ :PROTECTIONELIGIBILITY => :protection_eligibility,
33
+ :PROTECTIONELIGIBILITYTYPE => :protection_eligibility_type,
34
+ :ADDRESSOWNER => :address_owner,
35
+ :ADDRESSSTATUS => :address_status,
36
+ :INVNUM => :invoice_number,
37
+ :CUSTOM => :custom
38
+ }
39
+ attr_accessor *@@attribute_mapping.values
40
+ attr_accessor :shipping_options_is_default, :success_page_redirect_requested, :insurance_option_selected
41
+ attr_accessor :amount, :description, :ship_to, :bill_to, :payer, :recurring, :billing_agreement, :refund
42
+ attr_accessor :payment_responses, :payment_info, :items
43
+ alias_method :colleration_id, :correlation_id # NOTE: I made a typo :p
44
+
45
+ def initialize(attributes = {})
46
+ attrs = attributes.dup
47
+ @@attribute_mapping.each do |key, value|
48
+ self.send "#{value}=", attrs.delete(key)
49
+ end
50
+ @shipping_options_is_default = attrs.delete(:SHIPPINGOPTIONISDEFAULT) == 'true'
51
+ @success_page_redirect_requested = attrs.delete(:SUCCESSPAGEREDIRECTREQUESTED) == 'true'
52
+ @insurance_option_selected = attrs.delete(:INSURANCEOPTIONSELECTED) == 'true'
53
+ @amount = Payment::Common::Amount.new(
54
+ :total => attrs.delete(:AMT),
55
+ :item => attrs.delete(:ITEMAMT),
56
+ :handing => attrs.delete(:HANDLINGAMT),
57
+ :insurance => attrs.delete(:INSURANCEAMT),
58
+ :ship_disc => attrs.delete(:SHIPDISCAMT),
59
+ :shipping => attrs.delete(:SHIPPINGAMT),
60
+ :tax => attrs.delete(:TAXAMT),
61
+ :fee => attrs.delete(:FEEAMT)
62
+ )
63
+ @ship_to = Payment::Response::Address.new(
64
+ :owner => attrs.delete(:SHIPADDRESSOWNER),
65
+ :status => attrs.delete(:SHIPADDRESSSTATUS),
66
+ :name => attrs.delete(:SHIPTONAME),
67
+ :zip => attrs.delete(:SHIPTOZIP),
68
+ :street => attrs.delete(:SHIPTOSTREET),
69
+ :street2 => attrs.delete(:SHIPTOSTREET2),
70
+ :city => attrs.delete(:SHIPTOCITY),
71
+ :state => attrs.delete(:SHIPTOSTATE),
72
+ :country_code => attrs.delete(:SHIPTOCOUNTRYCODE),
73
+ :country_name => attrs.delete(:SHIPTOCOUNTRYNAME)
74
+ )
75
+ @bill_to = Payment::Response::Address.new(
76
+ :owner => attrs.delete(:ADDRESSID),
77
+ :status => attrs.delete(:ADDRESSSTATUS),
78
+ :name => attrs.delete(:BILLINGNAME),
79
+ :zip => attrs.delete(:ZIP),
80
+ :street => attrs.delete(:STREET),
81
+ :street2 => attrs.delete(:STREET2),
82
+ :city => attrs.delete(:CITY),
83
+ :state => attrs.delete(:STATE),
84
+ :country_code => attrs.delete(:COUNTRY)
85
+ )
86
+ if attrs[:PAYERID]
87
+ @payer = Payment::Response::Payer.new(
88
+ :identifier => attrs.delete(:PAYERID),
89
+ :status => attrs.delete(:PAYERSTATUS),
90
+ :first_name => attrs.delete(:FIRSTNAME),
91
+ :last_name => attrs.delete(:LASTNAME),
92
+ :email => attrs.delete(:EMAIL),
93
+ :company => attrs.delete(:BUSINESS)
94
+ )
95
+ end
96
+ if attrs[:PROFILEID]
97
+ @recurring = Payment::Recurring.new(
98
+ :identifier => attrs.delete(:PROFILEID),
99
+ # NOTE:
100
+ # CreateRecurringPaymentsProfile returns PROFILESTATUS
101
+ # GetRecurringPaymentsProfileDetails returns STATUS
102
+ :description => @description,
103
+ :status => attrs.delete(:STATUS) || attrs.delete(:PROFILESTATUS),
104
+ :start_date => attrs.delete(:PROFILESTARTDATE),
105
+ :name => attrs.delete(:SUBSCRIBERNAME),
106
+ :reference => attrs.delete(:PROFILEREFERENCE),
107
+ :auto_bill => attrs.delete(:AUTOBILLOUTAMT),
108
+ :max_fails => attrs.delete(:MAXFAILEDPAYMENTS),
109
+ :aggregate_amount => attrs.delete(:AGGREGATEAMT),
110
+ :aggregate_optional_amount => attrs.delete(:AGGREGATEOPTIONALAMT),
111
+ :final_payment_date => attrs.delete(:FINALPAYMENTDUEDATE)
112
+ )
113
+ if attrs[:BILLINGPERIOD]
114
+ @recurring.billing = Payment::Recurring::Billing.new(
115
+ :amount => @amount,
116
+ :currency_code => @currency_code,
117
+ :period => attrs.delete(:BILLINGPERIOD),
118
+ :frequency => attrs.delete(:BILLINGFREQUENCY),
119
+ :total_cycles => attrs.delete(:TOTALBILLINGCYCLES),
120
+ :trial => {
121
+ :period => attrs.delete(:TRIALBILLINGPERIOD),
122
+ :frequency => attrs.delete(:TRIALBILLINGFREQUENCY),
123
+ :total_cycles => attrs.delete(:TRIALTOTALBILLINGCYCLES),
124
+ :currency_code => attrs.delete(:TRIALCURRENCYCODE),
125
+ :amount => attrs.delete(:TRIALAMT),
126
+ :tax_amount => attrs.delete(:TRIALTAXAMT),
127
+ :shipping_amount => attrs.delete(:TRIALSHIPPINGAMT),
128
+ :paid => attrs.delete(:TRIALAMTPAID)
129
+ }
130
+ )
131
+ end
132
+ if attrs[:REGULARAMT]
133
+ @recurring.regular_billing = Payment::Recurring::Billing.new(
134
+ :amount => attrs.delete(:REGULARAMT),
135
+ :shipping_amount => attrs.delete(:REGULARSHIPPINGAMT),
136
+ :tax_amount => attrs.delete(:REGULARTAXAMT),
137
+ :currency_code => attrs.delete(:REGULARCURRENCYCODE),
138
+ :period => attrs.delete(:REGULARBILLINGPERIOD),
139
+ :frequency => attrs.delete(:REGULARBILLINGFREQUENCY),
140
+ :total_cycles => attrs.delete(:REGULARTOTALBILLINGCYCLES),
141
+ :paid => attrs.delete(:REGULARAMTPAID)
142
+ )
143
+ @recurring.summary = Payment::Recurring::Summary.new(
144
+ :next_billing_date => attrs.delete(:NEXTBILLINGDATE),
145
+ :cycles_completed => attrs.delete(:NUMCYCLESCOMPLETED),
146
+ :cycles_remaining => attrs.delete(:NUMCYCLESREMAINING),
147
+ :outstanding_balance => attrs.delete(:OUTSTANDINGBALANCE),
148
+ :failed_count => attrs.delete(:FAILEDPAYMENTCOUNT),
149
+ :last_payment_date => attrs.delete(:LASTPAYMENTDATE),
150
+ :last_payment_amount => attrs.delete(:LASTPAYMENTAMT)
151
+ )
152
+ end
153
+ end
154
+ if attrs[:BILLINGAGREEMENTID]
155
+ @billing_agreement = Payment::Response::Reference.new(
156
+ :identifier => attrs.delete(:BILLINGAGREEMENTID),
157
+ :description => attrs.delete(:BILLINGAGREEMENTDESCRIPTION),
158
+ :status => attrs.delete(:BILLINGAGREEMENTSTATUS)
159
+ )
160
+ billing_agreement_info = Payment::Response::Info.attribute_mapping.keys.inject({}) do |billing_agreement_info, key|
161
+ billing_agreement_info.merge! key => attrs.delete(key)
162
+ end
163
+ @billing_agreement.info = Payment::Response::Info.new billing_agreement_info
164
+ @billing_agreement.info.amount = @amount
165
+ end
166
+ if attrs[:REFUNDTRANSACTIONID]
167
+ @refund = Payment::Response::Refund.new(
168
+ :transaction_id => attrs.delete(:REFUNDTRANSACTIONID),
169
+ :amount => {
170
+ :total => attrs.delete(:TOTALREFUNDEDAMOUNT),
171
+ :fee => attrs.delete(:FEEREFUNDAMT),
172
+ :gross => attrs.delete(:GROSSREFUNDAMT),
173
+ :net => attrs.delete(:NETREFUNDAMT)
174
+ }
175
+ )
176
+ end
177
+
178
+ # payment_responses
179
+ payment_responses = []
180
+ attrs.keys.each do |_attr_|
181
+ prefix, index, key = case _attr_.to_s
182
+ when /^PAYMENTREQUEST/, /^PAYMENTREQUESTINFO/
183
+ _attr_.to_s.split('_')
184
+ when /^L_PAYMENTREQUEST/
185
+ _attr_.to_s.split('_')[1..-1]
186
+ end
187
+ if prefix
188
+ payment_responses[index.to_i] ||= {}
189
+ payment_responses[index.to_i][key.to_sym] = attrs.delete(_attr_)
190
+ end
191
+ end
192
+ @payment_responses = payment_responses.collect do |_attrs_|
193
+ Payment::Response.new _attrs_
194
+ end
195
+
196
+ # payment_info
197
+ payment_info = []
198
+ attrs.keys.each do |_attr_|
199
+ prefix, index, key = _attr_.to_s.split('_')
200
+ if prefix == 'PAYMENTINFO'
201
+ payment_info[index.to_i] ||= {}
202
+ payment_info[index.to_i][key.to_sym] = attrs.delete(_attr_)
203
+ end
204
+ end
205
+ @payment_info = payment_info.collect do |_attrs_|
206
+ Payment::Response::Info.new _attrs_
207
+ end
208
+
209
+ # payment_info
210
+ items = []
211
+ attrs.keys.each do |_attr_|
212
+ key, index = _attr_.to_s.scan(/^L_(.+?)(\d+)$/).flatten
213
+ if index
214
+ items[index.to_i] ||= {}
215
+ items[index.to_i][key.to_sym] = attrs.delete(_attr_)
216
+ end
217
+ end
218
+ @items = items.collect do |_attrs_|
219
+ Payment::Response::Item.new _attrs_
220
+ end
221
+
222
+ # remove duplicated parameters
223
+ attrs.delete(:SHIPTOCOUNTRY) # NOTE: Same with SHIPTOCOUNTRYCODE
224
+ attrs.delete(:SALESTAX) # Same as TAXAMT
225
+
226
+ # warn ignored attrs
227
+ attrs.each do |key, value|
228
+ Paypal.log "Ignored Parameter (#{self.class}): #{key}=#{value}", :warn
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,13 @@
1
+ module Express
2
+ module Payment
3
+ module Common
4
+ class Amount < Base
5
+ attr_optional :total, :item, :fee, :handing, :insurance, :ship_disc, :shipping, :tax, :gross, :net
6
+
7
+ def numeric_attribute?(key)
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ module Express
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
+ :AUTOBILLOUTAMT => 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 Express
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
@@ -0,0 +1,39 @@
1
+ module Express
2
+ module Payment
3
+ class Recurring::Billing < Base
4
+ attr_optional :period, :frequency, :paid, :currency_code, :total_cycles
5
+ attr_accessor :amount, :trial
6
+
7
+ def initialize(attributes = {})
8
+ @amount = if attributes[:amount].is_a?(Common::Amount)
9
+ attributes[:amount]
10
+ else
11
+ Common::Amount.new(
12
+ :total => attributes[:amount],
13
+ :tax => attributes[:tax_amount],
14
+ :shipping => attributes[:shipping_amount]
15
+ )
16
+ end
17
+ @trial = Recurring::Billing.new(attributes[:trial]) if attributes[:trial].present?
18
+ super
19
+ end
20
+
21
+ def to_params
22
+ trial_params = (trial.try(:to_params) || {}).inject({}) do |trial_params, (key, value)|
23
+ trial_params.merge(
24
+ :"TRIAL#{key}" => value
25
+ )
26
+ end
27
+ trial_params.merge(
28
+ :BILLINGPERIOD => self.period,
29
+ :BILLINGFREQUENCY => self.frequency,
30
+ :TOTALBILLINGCYCLES => self.total_cycles,
31
+ :AMT => Util.formatted_amount(self.amount.total),
32
+ :CURRENCYCODE => self.currency_code,
33
+ :SHIPPINGAMT => Util.formatted_amount(self.amount.shipping),
34
+ :TAXAMT => Util.formatted_amount(self.amount.tax)
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end