georgedrummond-active_paypal_adaptive_payment 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ ## 0 0.3.3 (16/Nov/11)
2
+
3
+ - add functionality to allow a user to redirect to the correct URL
4
+ when creating a pre-approval request. In addition, the
5
+ setup_purchase now allows for the preapproval_key to be submitted
6
+ and passed along to paypal. (LeviRosol (<https://github.com/LeviRosol>))
7
+
8
+ ## 0.3.2
9
+
10
+ - build_adaptive_payment_details_request pay_key fix.
11
+
12
+ ## 0.3.1
13
+
14
+ - Add Support of ExecutePayment.
15
+
16
+ ## 0.3.0
17
+
18
+ - IPN implemeted
19
+
20
+ ## 0.2.5
21
+
22
+ - Added custom filed to the request builder.
23
+
24
+ ## 0.2.4
25
+
26
+ - renamed the pay method to setup_purchase, again, more like active_merchant.
27
+ - added paypal_adaptive_payment_common.rb for handling the redirects more like active_merchant
28
+ - removed the autoloads, they look kind of messy
29
+
30
+ ## 0.2.3
31
+
32
+ - `autoload` the helper libs.
33
+
34
+ ## 0.2.2
35
+
36
+ - Array fix https://github.com/lsegal/yard/issues/370
37
+
38
+ ## 0.2.1
39
+
40
+ - Removed action pack as a dependency
41
+
42
+ ## 0.2.0
43
+
44
+ - Fix tests.
45
+ - Added fixtures.yml.
46
+
47
+ ## 0.1.2
48
+
49
+ - Few typo fixes by LeviRosol
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jose Pablo Barrantes
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 PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL 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.
@@ -0,0 +1,187 @@
1
+ # Active PayPal Adaptive Payment
2
+
3
+ This library is meant to interface with PayPal's Adaptive Payment Gateway.
4
+
5
+ [Active Merchant]:http://www.activemerchant.org
6
+
7
+ ![Active PayPal Adaptive Payment](https://github.com/jpablobr/active_paypal_adaptive_payment/raw/master/doc/split.jpg)
8
+
9
+ ## Supported
10
+
11
+ * Payments
12
+ * Peapprovals
13
+ * Refunds
14
+ * Currency conversions
15
+ * getting/setting payment options
16
+ * getting shipping addresses
17
+ * getting a redirect for the embedded pay flow
18
+ * More soon!
19
+
20
+ ## Installation
21
+
22
+ Add the following line to your app Gemfile:
23
+
24
+ gem "active_paypal_adaptive_payment"
25
+
26
+ bundle install
27
+
28
+ ## Implementation
29
+
30
+ See [iAuction: An Adaptive Payments Tutorial Featuring Parallel Payments](https://www.x.com/docs/DOC-2505) tutorial for more info.
31
+
32
+ ### Init
33
+
34
+ gateway = ActiveMerchant::Billing::PaypalAdaptivePayment.new(
35
+ :login => "acutio_1313133342_biz_api1.gmail.com",
36
+ :password => "1255043567",
37
+ :signature => "Abg0gYcQlsdkls2HDJkKtA-p6pqhA1k-KTYE0Gcy1diujFio4io5Vqjf",
38
+ :appid => "APP-80W284485P519543T" )
39
+
40
+ ### Pre-approved paymen
41
+
42
+ gateway.preapprove_payment (
43
+ :return_url => "returnURL",
44
+ :cancel_url => "cancelURL",
45
+ :senderEmail =>"email address of sender",
46
+ :start_date => Time.now,
47
+ :end_date => Time.now + (60*60*24) * 30,
48
+ :currency_code =>"currency code",
49
+ :max_amount => "maxTotalAmountOfAllPayments",
50
+ :maxNumberOfPayments => "maxNumberOfPayments" )
51
+
52
+ ### Cancel pre-approved payment
53
+
54
+ gateway.cancel_preapproval(:preapproval_key => "preapprovalkey")
55
+
56
+ ### Chained payments
57
+
58
+ def checkout
59
+ recipients = [{:email => 'receiver_email',
60
+ :amount => some_amount,
61
+ :primary => true},
62
+ {:email => 'receiver_email',
63
+ :amount => recipient_amount,
64
+ :primary => false}
65
+ ]
66
+ response = gateway.setup_purchase(
67
+ :return_url => url_for(:action => 'action', :only_path => false),
68
+ :cancel_url => url_for(:action => 'action', :only_path => false),
69
+ :ipn_notification_url => url_for(:action => 'notify_action', :only_path => false),
70
+ :receiver_list => recipients
71
+ )
72
+
73
+ # for redirecting the customer to the actual paypal site to finish the payment.
74
+ redirect_to (gateway.redirect_url_for(response["payKey"]))
75
+ end
76
+
77
+ Set the `:primary` flag to `false` for each recipient for a split payment.
78
+
79
+ Maybe also check the tests for a sample implementation.
80
+
81
+ Notes:
82
+
83
+ In `development` environment it's very important to define
84
+ ActiveMerchant environment you will be using to test your app like so:
85
+
86
+ ```ruby
87
+ #config/environments/{environment.rb}
88
+ App::Application.configure do
89
+ config.setting...
90
+ end
91
+ ActiveMerchant::Billing::Base.mode = :test
92
+ ```
93
+
94
+ ## Testing
95
+
96
+ First modify the `test/fixtures.yml` to fit your app credentials (You
97
+ will need at least a PayPal developer account).
98
+
99
+ After that you can run them like this:
100
+
101
+ $ ruby -Ilib test/test_paypal_adaptive_payment.rb
102
+
103
+ ## Debugging
104
+
105
+ Use either gateway.debug or response.debug this gives you the json
106
+ response, the xml sent and the url it was posted to.
107
+
108
+ From the Rails console it can be accessed like such:
109
+
110
+ ActiveMerchant::Billing::PaypalAdaptivePayment
111
+
112
+ `PaypalAdaptivePayment#debug` or `AdaptivePaymentResponse#debug` return the raw
113
+ xml request, raw json response and the URL of the endpoint.
114
+
115
+ ## TODO
116
+
117
+ * More/better Documentation.
118
+ * Improve/change the tests implementation by maybe run them off
119
+ fixture data and use a separate script/test for actually invoking
120
+ requests to PayPal. This will allow other developers to implement
121
+ the without being required to set and request the credentials from
122
+ PayPal process.
123
+
124
+ ## Contributors
125
+
126
+ * Jose Pablo Barrantes (<http://jpablobr.com/>)
127
+ * Zarne Dravitzki (<http://github.com/zarno>)
128
+ * LeviRosol (<https://github.com/LeviRosol>)
129
+ * Florian95 (<https://github.com/Florian95>)
130
+ * akichatov (<https://github.com/akichatov>)
131
+ * Patrick Sinclair (<https://github.com/metade>)
132
+ * Mike Pence (<https://github.com/mikepence>)
133
+
134
+ ## Other previous contributors where some code was taken from.
135
+
136
+ * [Tommy Chheng](http://tommy.chheng.com)
137
+ - [Paypal Adaptive Ruby Gem Released](http://tommy.chheng.com/2009/12/29/paypal-adaptive-ruby-gem-released/)
138
+ - [paypal_adaptive](https://github.com/tc/paypal_adaptive)
139
+
140
+ * [lamp (Matt)](https://github.com/lamp)
141
+ - [paypal_adaptive_gateway](https://github.com/lamp/paypal_adaptive_gateway)
142
+
143
+ * [sentientmonkey (Scott Windsor)](https://github.com/sentientmonkey)
144
+ - [active_merchant](https://github.com/sentientmonkey/active_merchant)
145
+
146
+ * [sijokg](https://github.com/sijokg)
147
+ - [active_merchant](https://github.com/sijokg/active_merchant)
148
+
149
+ ## Some PayPal Adaptive payment resources.
150
+
151
+ * [Adaptive Payment Fee Calculation Analysis](https://www.x.com/docs/DOC-2401)
152
+ * [ActiveMerchant paypaladaptive payments gateway](http://www.rorexperts.com/activemerchant-paypaladaptive-payments-gateway-t2245.html)
153
+ * [Trying to use with Paypal adaptive payments](http://groups.google.com/group/activemerchant/browse_thread/thread/866ad7dc5019c199/2a280b7dc396c41b?lnk=gst&q=adaptive+payment#2a280b7dc396c41b)
154
+ * [adpative payments (chained)](http://groups.google.com/group/activemerchant/browse_thread/thread/165c3e0bf4d10c02/aa8dd082b58354d9?lnk=gst&q=adaptive+payment#aa8dd082b58354d9)
155
+ * [Testing with a sandbox account without being logged on developer.paypal.com](http://groups.google.com/group/activemerchant/browse_thread/thread/ad69fc8116bfdf64/483f22071bb25e25?lnk=gst&q=adaptive+payment#483f22071bb25e25)
156
+ * [Split a transaction to distribute funds to two accounts?](http://groups.google.com/group/activemerchant/browse_thread/thread/e1f53087aee9d0c/2cd63df363861ce1?lnk=gst&q=adaptive+payment#2cd63df363861ce1)
157
+
158
+ ## Note on Patches/Pull Requests:
159
+
160
+ * Fork the project.
161
+ * Make your feature addition or bug fix.
162
+ * Send me a pull request. Bonus points for topic branches.
163
+
164
+ ## Copyright:
165
+
166
+ (The MIT License)
167
+
168
+ Copyright 2011 Jose Pablo Barrantes. MIT Licence, so go for it.
169
+
170
+ Permission is hereby granted, free of charge, to any person obtaining a
171
+ copy of this software and associated documentation files (the
172
+ 'Software'), to deal in the Software without restriction, including
173
+ without limitation the rights to use, copy, modify, merge, publish,
174
+ distribute, sublicense, an d/or sell copies of the Software, and to
175
+ permit persons to whom the Software is furnished to do so, subject to
176
+ the following conditions:
177
+
178
+ The above copyright notice and this permission notice shall be included
179
+ in all copies or substantial portions of the Software.
180
+
181
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
182
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
183
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
184
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
185
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
186
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
187
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,374 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'money'
3
+ require File.dirname(__FILE__) + '/paypal_adaptive_payments/ext'
4
+ require File.dirname(__FILE__) + '/paypal_adaptive_payment_common'
5
+ require File.dirname(__FILE__) + '/paypal_adaptive_payments/exceptions'
6
+ require File.dirname(__FILE__) + '/paypal_adaptive_payments/adaptive_payment_response'
7
+
8
+ module ActiveMerchant #:nodoc:
9
+ module Billing #:nodoc:
10
+
11
+ class PaypalAdaptivePayment < Gateway # :nodoc
12
+ include PaypalAdaptivePaymentCommon
13
+
14
+ TEST_URL = 'https://svcs.sandbox.paypal.com/AdaptivePayments/'
15
+ LIVE_URL = 'https://svcs.paypal.com/AdaptivePayments/'
16
+
17
+ EMBEDDED_FLOW_TEST_URL = 'https://www.sandbox.paypal.com/webapps/adaptivepayment/flow/pay'
18
+ EMBEDDED_FLOW_LIVE_URL = 'https://www.paypal.com/webapps/adaptivepayment/flow/pay'
19
+
20
+ module FeesPayer
21
+ OPTIONS = %w(SENDER PRIMARYRECEIVER EACHRECEIVER SECONDARYONLY)
22
+ OPTIONS.each { |opt| const_set(opt, opt) }
23
+ end
24
+
25
+ module PaymentType
26
+ TYPES = %w(DIGITALGOODS)
27
+ TYPES.each { |pt| const_set(pt, pt) }
28
+ end
29
+
30
+ self.test_redirect_url= "https://www.sandbox.paypal.com/webscr?cmd=_ap-payment&paykey="
31
+ self.test_redirect_pre_approval_url= "https://www.sandbox.paypal.com/webscr?cmd=_ap-preapproval&preapprovalkey="
32
+ self.supported_countries = ['US']
33
+ self.homepage_url = 'http://x.com/'
34
+ self.display_name = 'Paypal Adaptive Payments'
35
+
36
+ def initialize(config = {})
37
+ requires!(config, :login, :password, :signature, :appid)
38
+ @config = config.dup
39
+ super
40
+ end
41
+
42
+ def setup_purchase(options)
43
+ commit('Pay', build_adaptive_payment_pay_request(options))
44
+ end
45
+
46
+ def details_for_payment(options)
47
+ commit('PaymentDetails', build_adaptive_payment_details_request(options))
48
+ end
49
+
50
+ def get_shipping_addresses(options)
51
+ commit('GetShippingAddresses', build_adaptive_get_shipping_addresses_request(options))
52
+ end
53
+
54
+ def get_payment_options(options)
55
+ commit('GetPaymentOptions', build_adaptive_get_payment_options_request(options))
56
+ end
57
+
58
+ def set_payment_options(options)
59
+ commit('SetPaymentOptions', build_adaptive_set_payment_options_request(options))
60
+ end
61
+
62
+ def refund(options)
63
+ commit('Refund', build_adaptive_refund_details(options))
64
+ end
65
+
66
+ def execute_payment(options)
67
+ commit('ExecutePayment', build_adaptive_execute_payment_request(options))
68
+ end
69
+
70
+ def preapprove_payment(options)
71
+ commit('Preapproval', build_preapproval_payment(options))
72
+ end
73
+
74
+ def cancel_preapproval(options)
75
+ commit('CancelPreapproval', build_cancel_preapproval(options))
76
+ end
77
+
78
+ def preapproval_details_for(options)
79
+ commit('PreapprovalDetails', build_preapproval_details(options))
80
+ end
81
+
82
+ def convert_currency(options)
83
+ commit('ConvertCurrency', build_currency_conversion(options))
84
+ end
85
+
86
+ def embedded_flow_url
87
+ test? ? EMBEDDED_FLOW_TEST_URL : EMBEDDED_FLOW_LIVE_URL
88
+ end
89
+
90
+ def embedded_flow_url_for(token)
91
+ "#{embedded_flow_url}?paykey=#{token}"
92
+ end
93
+
94
+ def debug
95
+ "Url: #{@url}\n\n Request: #{@xml} \n\n Response: #{@response.json}"
96
+ end
97
+
98
+ private
99
+
100
+ def build_adaptive_payment_pay_request(opts)
101
+ @xml = ''
102
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
103
+ xml.instruct!
104
+ xml.PayRequest do |x|
105
+ x.requestEnvelope do |x|
106
+ x.detailLevel 'ReturnAll'
107
+ x.errorLanguage opts[:error_language] ||= 'en_US'
108
+ end
109
+ x.actionType 'PAY'
110
+ x.preapprovalKey opts[:preapproval_key] if opts.key?(:preapproval_key)
111
+ x.senderEmail opts[:sender_email] if opts.key?(:sender_email)
112
+ x.cancelUrl opts[:cancel_url]
113
+ x.returnUrl opts[:return_url]
114
+ x.ipnNotificationUrl opts[:ipn_notification_url] if
115
+ opts[:ipn_notification_url]
116
+ x.memo opts[:memo] if opts.key?(:memo)
117
+ x.custom opts[:custom] if opts.key?(:custom)
118
+ x.feesPayer opts[:fees_payer] if opts[:fees_payer]
119
+ x.pin opts[:pin] if opts[:pin]
120
+ x.currencyCode opts[:currency_code] ||= 'USD'
121
+ x.receiverList do |x|
122
+ opts[:receiver_list].each do |receiver|
123
+ x.receiver do |x|
124
+ x.email receiver[:email]
125
+ x.amount receiver[:amount].to_s
126
+ x.primary receiver[:primary] if receiver.key?(:primary)
127
+ x.paymentType receiver[:payment_type] if
128
+ receiver.key?(:payment_type)
129
+ x.invoiceId receiver[:invoice_id] if
130
+ receiver.key?(:invoice_id)
131
+ end
132
+ end
133
+ end
134
+ x.reverseAllParallelPaymentsOnError(
135
+ opts[:reverse_all_parallel_payments_on_error] || 'false')
136
+ x.trackingId opts[:tracking_id] if opts[:tracking_id]
137
+ end
138
+ end
139
+
140
+ def build_adaptive_execute_payment_request(opts)
141
+ @xml = ''
142
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
143
+ xml.instruct!
144
+ xml.ExecutePaymentRequest do |x|
145
+ x.requestEnvelope do |x|
146
+ x.detailLevel 'ReturnAll'
147
+ x.errorLanguage opts[:error_language] ||= 'en_US'
148
+ end
149
+ x.payKey opts[:pay_key] if opts.key?(:pay_key)
150
+ x.fundingPlanId opts[:funding_plan_id] if opts[:funding_plan_id]
151
+ end
152
+ end
153
+
154
+ def build_adaptive_payment_details_request(opts)
155
+ @xml = ''
156
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
157
+ xml.instruct!
158
+ xml.PayRequest do |x|
159
+ x.requestEnvelope do |x|
160
+ x.detailLevel 'ReturnAll'
161
+ x.errorLanguage opts[:error_language] ||= 'en_US'
162
+ end
163
+ x.payKey opts[:pay_key]
164
+ end
165
+ end
166
+
167
+ def build_adaptive_get_shipping_addresses_request(opts)
168
+ @xml = ''
169
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
170
+ xml.instruct!
171
+ xml.GetShippingAddressesRequest do |x|
172
+ x.requestEnvelope do |x|
173
+ x.detailLevel 'ReturnAll'
174
+ x.errorLanguage opts[:error_language] ||= 'en_US'
175
+ end
176
+ x.key opts[:pay_key]
177
+ end
178
+ end
179
+
180
+ def build_adaptive_get_payment_options_request(opts)
181
+ @xml = ''
182
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
183
+ xml.instruct!
184
+ xml.GetPaymentOptionsRequest do |x|
185
+ x.requestEnvelope do |x|
186
+ x.detailLevel 'ReturnAll'
187
+ x.errorLanguage opts[:error_language] ||= 'en_US'
188
+ end
189
+ x.payKey opts[:pay_key]
190
+ end
191
+ end
192
+
193
+ def build_adaptive_set_payment_options_request(opts)
194
+ opts[:sender] ||= {}
195
+
196
+ @xml = ''
197
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
198
+ xml.instruct!
199
+ xml.SetPaymentOptionsRequest do |x|
200
+ x.requestEnvelope do |x|
201
+ x.detailLevel 'ReturnAll'
202
+ x.errorLanguage opts[:error_language] ||= 'en_US'
203
+ end
204
+ x.senderOptions do |x|
205
+ x.shareAddress opts[:sender][:share_address] if opts[:sender][:share_address]
206
+ x.sharePhoneNumber opts[:sender][:share_phone_number] if opts[:sender][:share_phone_number]
207
+ x.requireShippingAddressSelection opts[:sender][:require_shipping_address_selection] if opts[:sender][:require_shipping_address_selection]
208
+ end
209
+ x.payKey opts[:pay_key]
210
+ end
211
+ end
212
+
213
+ def build_adaptive_refund_details(options)
214
+ @xml = ''
215
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
216
+ xml.instruct!
217
+ xml.RefundRequest do |x|
218
+ x.requestEnvelope do |x|
219
+ x.detailLevel 'ReturnAll'
220
+ x.errorLanguage options[:error_language] ||= 'en_US'
221
+ end
222
+ x.actionType 'REFUND'
223
+ if options[:pay_key]
224
+ x.payKey options[:pay_key]
225
+ end
226
+ if options[:transaction_id]
227
+ x.payKey options[:transaction_id]
228
+ end
229
+ if options[:tracking_id]
230
+ x.trackingId options[:tracking_id]
231
+ end
232
+ x.cancelUrl options[:cancel_url]
233
+ x.returnUrl options[:return_url]
234
+ x.currencyCode options[:currency_code] ||= 'USD'
235
+ x.receiverList do |x|
236
+ options[:receiver_list].each do |receiver|
237
+ x.receiver do |x|
238
+ x.amount receiver[:amount]
239
+ x.paymentType receiver[:payment_type] ||= 'GOODS'
240
+ x.invoiceId receiver[:invoice_id] if receiver[:invoice_id]
241
+ x.email receiver[:email]
242
+ end
243
+ end
244
+ end
245
+ x.feesPayer options[:fees_payer] ||= 'EACHRECEIVER'
246
+ end
247
+ end
248
+
249
+ def build_preapproval_payment(options)
250
+ opts = {
251
+ :currency_code => "USD",
252
+ :start_date => DateTime.current
253
+ }.update(options)
254
+
255
+ @xml = ''
256
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
257
+ xml.instruct!
258
+ xml.PreapprovalRequest do |x|
259
+ # request envelope
260
+ x.requestEnvelope do |x|
261
+ x.detailLevel 'ReturnAll'
262
+ x.errorLanguage opts[:error_language] ||= 'en_US'
263
+ x.senderEmail opts[:senderEmail] if opts.has_key?(:senderEmail)
264
+ end
265
+
266
+ # required preapproval fields
267
+ x.endingDate opts[:end_date].strftime("%Y-%m-%dT%H:%M:%S")
268
+ x.startingDate opts[:start_date].strftime("%Y-%m-%dT%H:%M:%S")
269
+ x.maxTotalAmountOfAllPayments opts[:max_amount]
270
+ x.maxAmountPerPayment opts[:maxAmountPerPayment] if opts.has_key?(:maxAmountPerPayment)
271
+ x.memo opts[:memo] if opts.has_key?(:memo)
272
+ x.maxNumberOfPayments opts[:maxNumberOfPayments] if
273
+ opts.has_key?(:maxNumberOfPayments)
274
+ x.currencyCode options[:currency_code]
275
+ x.cancelUrl opts[:cancel_url]
276
+ x.returnUrl opts[:return_url]
277
+
278
+ # notify url
279
+ x.ipnNotificationUrl opts[:notify_url] if
280
+ opts.has_key?(:notify_url)
281
+ end
282
+ end
283
+
284
+ def build_preapproval_details(options)
285
+ @xml = ''
286
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
287
+ xml.instruct!
288
+ xml.PreapprovalDetailsRequest do |x|
289
+ x.requestEnvelope do |x|
290
+ x.detailLevel 'ReturnAll'
291
+ x.errorLanguage options[:error_language] ||= 'en_US'
292
+ end
293
+ x.preapprovalKey options[:preapproval_key]
294
+ x.getBillingAddress options[:get_billing_address] if
295
+ options[:get_billing_address]
296
+ end
297
+ end
298
+
299
+ def build_cancel_preapproval(options)
300
+ @xml = ''
301
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
302
+ xml.instruct!
303
+ xml.PreapprovalDetailsRequest do |x|
304
+ x.requestEnvelope do |x|
305
+ x.detailLevel 'ReturnAll'
306
+ x.errorLanguage options[:error_language] ||= 'en_US'
307
+ end
308
+ x.preapprovalKey options[:preapproval_key]
309
+ end
310
+ end
311
+
312
+ def build_currency_conversion(options)
313
+ @xml = ''
314
+ xml = Builder::XmlMarkup.new :target => @xml, :indent => 2
315
+ xml.instruct!
316
+ xml.ConvertCurrencyRequest do |x|
317
+ x.requestEnvelope do |x|
318
+ x.detailLevel 'ReturnAll'
319
+ x.errorLanguage options[:error_language] ||= 'en_US'
320
+ end
321
+ x.baseAmountList do |x|
322
+ options[:currency_list].each do |currency|
323
+ x.currency do |x|
324
+ x.amount currency[:amount]
325
+ x.code currency[:code ]
326
+ end
327
+ end
328
+ end
329
+ x.convertToCurrencyList do |x|
330
+ options[:to_currencies].each do |k,v|
331
+ x.currencyCode "#{v}"
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ def commit(action, data)
338
+ @response = AdaptivePaymentResponse.new(post_through_ssl(action, data))
339
+ end
340
+
341
+ def post_through_ssl(action, parameters = {})
342
+ headers = {
343
+ "X-PAYPAL-REQUEST-DATA-FORMAT" => "XML",
344
+ "X-PAYPAL-RESPONSE-DATA-FORMAT" => "JSON",
345
+ "X-PAYPAL-SECURITY-USERID" => @config[:login],
346
+ "X-PAYPAL-SECURITY-PASSWORD" => @config[:password],
347
+ "X-PAYPAL-SECURITY-SIGNATURE" => @config[:signature],
348
+ "X-PAYPAL-APPLICATION-ID" => @config[:appid],
349
+ }
350
+ action_url(action)
351
+ request = Net::HTTP::Post.new(@url.path)
352
+ request.body = @xml
353
+ headers.each_pair { |k,v| request[k] = v }
354
+ request.content_type = 'text/xml'
355
+ server = Net::HTTP.new(@url.host, 443)
356
+ server.use_ssl = true
357
+ server.start { |http| http.request(request) }.body
358
+ end
359
+
360
+ def endpoint_url
361
+ test? ? TEST_URL : LIVE_URL
362
+ end
363
+
364
+ def test?
365
+ @config[:test] || Base.gateway_mode == :test
366
+ end
367
+
368
+ def action_url(action)
369
+ @url = URI.parse(endpoint_url + action)
370
+ end
371
+
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ module PaypalAdaptivePaymentCommon
4
+ def self.included(base)
5
+ base.cattr_accessor :test_redirect_url
6
+ base.cattr_accessor :live_redirect_url
7
+ base.cattr_accessor :test_redirect_pre_approval_url
8
+ base.cattr_accessor :live_redirect_pre_approval_url
9
+ base.live_redirect_url = 'https://www.paypal.com/webscr?cmd=_ap-payment&paykey='
10
+ base.live_redirect_pre_approval_url = 'https://www.paypal.com/webscr?cmd=_ap-preapproval&preapprovalkey='
11
+ end
12
+
13
+ def redirect_url
14
+ test? ? test_redirect_url : live_redirect_url
15
+ end
16
+
17
+ # TODO: validate the token presence
18
+ def redirect_url_for(token)
19
+ "#{redirect_url}#{token}"
20
+ end
21
+
22
+ def redirect_pre_approval_url
23
+ test? ? test_redirect_pre_approval_url : live_redirect_pre_approval_url
24
+ end
25
+
26
+ def redirect_pre_approval_url_for(token)
27
+ "#{redirect_pre_approval_url}#{token}"
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,48 @@
1
+ require 'multi_json'
2
+ require 'hashie'
3
+
4
+ module ActiveMerchant
5
+ module Billing
6
+ class AdaptivePaymentResponse < Response
7
+
8
+ SUCCESS = 'Success'.freeze
9
+
10
+ attr_reader :json
11
+ alias :raw :json
12
+
13
+ def initialize(json)
14
+ @json = json
15
+ @response_rash = Hashie::Rash.new(MultiJson.decode(json))
16
+ end
17
+
18
+ def method_missing(method, *args, &block)
19
+ @response_rash.send(method, *args, &block)
20
+ end
21
+
22
+ # def redirect_url_for
23
+ # Base.gateway_mode == :test ? (TEST_REDIRECT_URL + pay_key) : (REDIRECT_URL + pay_key)
24
+ # end
25
+
26
+ def ack
27
+ response_envelope.ack
28
+ end
29
+
30
+ def timestamp
31
+ response_envelope.timestamp
32
+ end
33
+
34
+ def build
35
+ response_envelope.build
36
+ end
37
+
38
+ def correlation_id
39
+ response_envelope.correlation_id
40
+ end
41
+ alias :correlationId :correlation_id
42
+
43
+ def success?
44
+ ack == SUCCESS
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ class ConfigDoesNotExist < StandardError; end;
2
+ class AttributenotFound < StandardError; end;
3
+
4
+ class PaypalAdaptivePaymentsApiError < StandardError
5
+
6
+ attr_reader :response
7
+
8
+ def initialize response
9
+ @response = response
10
+ end
11
+
12
+ def debug
13
+ @response.inspect
14
+ end
15
+
16
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # https://github.com/tallgreentree/rash/blob/master/lib/hashie/rash.rb
3
+
4
+ require 'hashie/mash'
5
+
6
+ module Hashie
7
+ class Rash < Mash
8
+
9
+ protected
10
+
11
+ def convert_key(key)
12
+ underscore_string(key.to_s)
13
+ end
14
+
15
+ # converts a camel_cased string to a underscore string
16
+ # subs spaces with underscores, strips whitespace
17
+ # Same way ActiveSupport does string.underscore
18
+ def underscore_string(str)
19
+ str.to_s.strip.
20
+ gsub(' ', '_').
21
+ gsub(/::/, '/').
22
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
23
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
24
+ tr("-", "_").
25
+ squeeze("_").
26
+ downcase
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module ActiveMerchant #:nodoc:
3
+ module Billing #:nodoc:
4
+ module Integrations #:nodoc:
5
+ class Notification
6
+
7
+ private
8
+
9
+ # Take the posted data and move the relevant data into a hash
10
+ def parse(post)
11
+ @raw = post.to_s
12
+ for line in @raw.split('&')
13
+ key, value = CGI.unescape(*line).scan( %r{^([A-Za-z0-9_.\[\]]+)\=(.*)$} ).flatten
14
+ params[key] = value
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ module ActiveMerchant #:nodoc:
3
+ module Billing #:nodoc:
4
+ module Integrations #:nodoc:
5
+ module PaypalAdaptivePayment
6
+ autoload :Return, 'active_merchant/billing/integrations/paypal_adaptive_payment/return.rb'
7
+ autoload :Helper, 'active_merchant/billing/integrations/paypal_adaptive_payment/helper.rb'
8
+ autoload :Notification, 'active_merchant/billing/integrations/paypal_adaptive_payment/notification.rb'
9
+
10
+ # Overwrite this if you want to change the Paypal test url
11
+ mattr_accessor :test_url
12
+ self.test_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
13
+
14
+ # Overwrite this if you want to change the Paypal production url
15
+ mattr_accessor :production_url
16
+ self.production_url = 'https://www.paypal.com/cgi-bin/webscr'
17
+
18
+ def self.service_url
19
+ mode = ActiveMerchant::Billing::Base.integration_mode
20
+ case mode
21
+ when :production
22
+ self.production_url
23
+ when :test
24
+ self.test_url
25
+ else
26
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
27
+ end
28
+ end
29
+
30
+ def self.notification(post, options = {})
31
+ Notification.new(post)
32
+ end
33
+
34
+ def self.return(query_string, options = {})
35
+ Return.new(query_string)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ module ActiveMerchant #:nodoc:
3
+ module Billing #:nodoc:
4
+ module Integrations #:nodoc:
5
+ module PaypalAdaptivePayment
6
+ class Helper < ActiveMerchant::Billing::Integrations::Paypal::Helper
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,130 @@
1
+ # encoding: utf-8
2
+ require 'net/http'
3
+
4
+ module ActiveMerchant #:nodoc:
5
+ module Billing #:nodoc:
6
+ module Integrations #:nodoc:
7
+ module PaypalAdaptivePayment
8
+ # Parser and handler for incoming Instant payment notifications from paypal.
9
+ # The Example shows a typical handler in a rails application. Note that this
10
+ # is an example, please read the Paypal API documentation for all the details
11
+ # on creating a safe payment controller.
12
+ #
13
+ # Example
14
+ #
15
+ # class BackendController < ApplicationController
16
+ # include ActiveMerchant::Billing::Integrations
17
+ #
18
+ # def paypal_ipn
19
+ # notify = PaypalAdaptivePayment::Notification.new(request.raw_post)
20
+ #
21
+ # order = Order.find(notify.item_id)
22
+ #
23
+ # if notify.acknowledge
24
+ # begin
25
+ #
26
+ # if notify.complete? and order.total == notify.amount
27
+ # order.status = 'COMPLETED'
28
+ #
29
+ # shop.ship(order)
30
+ # else
31
+ # logger.error("Failed to verify Paypal's notification, please investigate")
32
+ # end
33
+ #
34
+ # rescue => e
35
+ # order.status = 'ERROR'
36
+ # raise
37
+ # ensure
38
+ # order.save
39
+ # end
40
+ # end
41
+ #
42
+ # render :nothing
43
+ # end
44
+ # end
45
+ class Notification < ActiveMerchant::Billing::Integrations::Notification
46
+ include PostsData
47
+
48
+ # Was the transaction complete?
49
+ def complete?
50
+ status == "COMPLETED"
51
+ end
52
+
53
+ # Status of transaction. List of possible values:
54
+ # <tt>CREATED</tt>::
55
+ # <tt>COMPLETED</tt>::
56
+ # <tt>INCOMPLETE</tt>::
57
+ # <tt>ERROR</tt>::
58
+ # <tt>REVERSALERROR</tt>::
59
+ # <tt>PROCESSING</tt>::
60
+ # <tt>PENDING</tt>::
61
+ def status
62
+ params['status']
63
+ end
64
+
65
+ # Id of this transaction (paypal number)
66
+ def transaction_id
67
+ params['transaction[0].id_for_sender_txn']
68
+ end
69
+
70
+ def type
71
+ params['action_type']
72
+ end
73
+
74
+ # This is the item number which we submitted to paypal
75
+ # The custom field is also mapped to item_id because PayPal
76
+ # doesn't return item_number in dispute notifications
77
+ def item_id
78
+ params['item_number'] || params['custom']
79
+ end
80
+
81
+ # This is the invoice which you passed to paypal
82
+ def invoice
83
+ params['transaction[0].invoiceId']
84
+ end
85
+
86
+ # This is the amount which you passed to paypal
87
+ def amount
88
+ params['transaction[0].amount']
89
+ end
90
+
91
+ # Was this a test transaction?
92
+ def test?
93
+ params['test_ipn'] == '1'
94
+ end
95
+
96
+ def account
97
+ params['business'] || params['transaction[0].receiver']
98
+ end
99
+
100
+ # Acknowledge the transaction to paypal. This method has to be called after a new
101
+ # ipn arrives. Paypal will verify that all the information we received are correct and will return a
102
+ # ok or a fail.
103
+ #
104
+ # Example:
105
+ #
106
+ # def paypal_ipn
107
+ # notify = PaypalAdaptivePaymentNotification.new(request.raw_post)
108
+ #
109
+ # if notify.acknowledge
110
+ # ... process order ... if notify.complete?
111
+ # else
112
+ # ... log possible hacking attempt ...
113
+ # end
114
+ def acknowledge
115
+ payload = raw
116
+
117
+ response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload,
118
+ 'Content-Length' => "#{payload.size}",
119
+ 'User-Agent' => "Active Merchant -- http://activemerchant.org"
120
+ )
121
+
122
+ raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response)
123
+
124
+ response == "VERIFIED"
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ module Integrations #:nodoc:
4
+ module PaypalAdaptivePayment
5
+ class Return < ActiveMerchant::Billing::Integrations::Return
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ require 'active_merchant'
3
+ require File.dirname(__FILE__) + '/active_merchant/billing/gateways/paypal_adaptive_payment'
4
+ require File.dirname(__FILE__) + '/active_merchant/billing/integrations/paypal_adaptive_payment'
5
+ require File.dirname(__FILE__) + '/active_merchant/billing/integrations/notification'
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: georgedrummond-active_paypal_adaptive_payment
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.9
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jose Pablo Barrantes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-12 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activemerchant
16
+ requirement: &70339597920540 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70339597920540
25
+ - !ruby/object:Gem::Dependency
26
+ name: multi_json
27
+ requirement: &70339597920040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70339597920040
36
+ - !ruby/object:Gem::Dependency
37
+ name: hashie
38
+ requirement: &70339597919500 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70339597919500
47
+ - !ruby/object:Gem::Dependency
48
+ name: money
49
+ requirement: &70339597919000 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 3.6.0
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70339597919000
58
+ - !ruby/object:Gem::Dependency
59
+ name: mocha
60
+ requirement: &70339597918480 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 0.10.0
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70339597918480
69
+ description: ! ' This library is meant to interface with PayPal''s Adaptive Payment
70
+ Gateway.
71
+
72
+ '
73
+ email:
74
+ - xjpablobrx@gmail.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - lib/active_merchant/billing/gateways/paypal_adaptive_payment.rb
80
+ - lib/active_merchant/billing/gateways/paypal_adaptive_payment_common.rb
81
+ - lib/active_merchant/billing/gateways/paypal_adaptive_payments/adaptive_payment_response.rb
82
+ - lib/active_merchant/billing/gateways/paypal_adaptive_payments/exceptions.rb
83
+ - lib/active_merchant/billing/gateways/paypal_adaptive_payments/ext.rb
84
+ - lib/active_merchant/billing/integrations/notification.rb
85
+ - lib/active_merchant/billing/integrations/paypal_adaptive_payment/helper.rb
86
+ - lib/active_merchant/billing/integrations/paypal_adaptive_payment/notification.rb
87
+ - lib/active_merchant/billing/integrations/paypal_adaptive_payment/return.rb
88
+ - lib/active_merchant/billing/integrations/paypal_adaptive_payment.rb
89
+ - lib/active_paypal_adaptive_payment.rb
90
+ - MIT-LICENSE
91
+ - README.md
92
+ - CHANGELOG.md
93
+ homepage: http://github.com/jpablobr/active_paypal_adaptive_payment
94
+ licenses: []
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.3.6
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 1.8.11
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: ActiveMercant PayPal Adaptive Payment Library
117
+ test_files: []