funddit_paypal_express 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.lock +146 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +64 -0
  9. data/Rakefile +38 -0
  10. data/app/assets/javascripts/catarse_paypal_express.js +6 -0
  11. data/app/assets/javascripts/catarse_paypal_express/paypal_form.js +19 -0
  12. data/app/assets/javascripts/catarse_paypal_express/user_document.js +111 -0
  13. data/app/controllers/.gitkeep +0 -0
  14. data/app/controllers/catarse_paypal_express/paypal_express_controller.rb +113 -0
  15. data/app/views/catarse_paypal_express/paypal_express/review.html.slim +20 -0
  16. data/catarse_paypal_express.gemspec +27 -0
  17. data/config/initializers/active_merchant.rb +2 -0
  18. data/config/initializers/register.rb +5 -0
  19. data/config/locales/en.yml +17 -0
  20. data/config/locales/pt.yml +17 -0
  21. data/config/routes.rb +15 -0
  22. data/lib/catarse_paypal_express.rb +8 -0
  23. data/lib/catarse_paypal_express/contribution_actions.rb +20 -0
  24. data/lib/catarse_paypal_express/engine.rb +6 -0
  25. data/lib/catarse_paypal_express/gateway.rb +36 -0
  26. data/lib/catarse_paypal_express/payment_engine.rb +25 -0
  27. data/lib/catarse_paypal_express/version.rb +3 -0
  28. data/lib/tasks/catarse_paypal_express_tasks.rake +4 -0
  29. data/script/rails +9 -0
  30. data/spec/controllers/catarse_paypal_express/paypal_express_controller_spec.rb +363 -0
  31. data/spec/fixtures/ipn_data.txt +1 -0
  32. data/spec/lib/catarse_paypal_express/contribution_actions_spec.rb +41 -0
  33. data/spec/spec_helper.rb +61 -0
  34. data/spec/support/payment_engines.rb +5 -0
  35. data/test/dummy/README.rdoc +261 -0
  36. data/test/dummy/Rakefile +7 -0
  37. data/test/dummy/app/assets/javascripts/application.js +15 -0
  38. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  39. data/test/dummy/app/controllers/application_controller.rb +3 -0
  40. data/test/dummy/app/helpers/application_helper.rb +2 -0
  41. data/test/dummy/app/mailers/.gitkeep +0 -0
  42. data/test/dummy/app/models/.gitkeep +0 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/config/application.rb +23 -0
  46. data/test/dummy/config/boot.rb +10 -0
  47. data/test/dummy/config/database.yml +50 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +29 -0
  50. data/test/dummy/config/environments/production.rb +80 -0
  51. data/test/dummy/config/environments/test.rb +36 -0
  52. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  53. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  54. data/test/dummy/config/initializers/inflections.rb +16 -0
  55. data/test/dummy/config/initializers/mime_types.rb +5 -0
  56. data/test/dummy/config/initializers/secret_token.rb +12 -0
  57. data/test/dummy/config/initializers/session_store.rb +3 -0
  58. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  59. data/test/dummy/config/locales/en.yml +5 -0
  60. data/test/dummy/config/routes.rb +4 -0
  61. data/test/dummy/lib/assets/.gitkeep +0 -0
  62. data/test/dummy/log/.gitkeep +0 -0
  63. data/test/dummy/public/404.html +26 -0
  64. data/test/dummy/public/422.html +26 -0
  65. data/test/dummy/public/500.html +25 -0
  66. data/test/dummy/public/favicon.ico +0 -0
  67. data/test/dummy/script/rails +6 -0
  68. metadata +231 -0
File without changes
@@ -0,0 +1,113 @@
1
+ class CatarsePaypalExpress::PaypalExpressController < ApplicationController
2
+ include ActiveMerchant::Billing::Integrations
3
+
4
+ skip_before_filter :force_http
5
+ SCOPE = "projects.contributions.checkout"
6
+ layout :false
7
+
8
+ def review
9
+ end
10
+
11
+ def ipn
12
+ if contribution && notification.acknowledge && (contribution.payment_method == 'PayPal' || contribution.payment_method.nil?)
13
+ process_paypal_message params
14
+ contribution.update_attributes({
15
+ :payment_service_fee => params['mc_fee'],
16
+ :payer_email => params['payer_email']
17
+ })
18
+ else
19
+ return render status: 500, nothing: true
20
+ end
21
+ return render status: 200, nothing: true
22
+ rescue Exception => e
23
+ return render status: 500, text: e.inspect
24
+ end
25
+
26
+ def pay
27
+ begin
28
+ response = gateway.setup_purchase(contribution.price_in_cents, {
29
+ ip: request.remote_ip,
30
+ return_url: success_paypal_express_url(id: contribution.id),
31
+ cancel_return_url: cancel_paypal_express_url(id: contribution.id),
32
+ currency_code: 'BRL',
33
+ description: t('paypal_description', scope: SCOPE, :project_name => contribution.project.name, :value => contribution.display_value),
34
+ notify_url: ipn_paypal_express_index_url
35
+ })
36
+
37
+ process_paypal_message response.params
38
+ contribution.update_attributes payment_method: 'PayPal', payment_token: response.token
39
+
40
+ redirect_to gateway.redirect_url_for(response.token)
41
+ rescue Exception => e
42
+ Rails.logger.info "-----> #{e.inspect}"
43
+ flash[:failure] = t('paypal_error', scope: SCOPE)
44
+ return redirect_to main_app.new_project_contribution_path(contribution.project)
45
+ end
46
+ end
47
+
48
+ def success
49
+ begin
50
+ purchase = gateway.purchase(contribution.price_in_cents, {
51
+ ip: request.remote_ip,
52
+ token: contribution.payment_token,
53
+ payer_id: params[:PayerID]
54
+ })
55
+
56
+ # we must get the deatils after the purchase in order to get the transaction_id
57
+ process_paypal_message purchase.params
58
+ contribution.update_attributes payment_id: purchase.params['transaction_id'] if purchase.params['transaction_id']
59
+
60
+ flash[:success] = t('success', scope: SCOPE)
61
+ redirect_to main_app.project_contribution_path(project_id: contribution.project.id, id: contribution.id)
62
+ rescue Exception => e
63
+ Rails.logger.info "-----> #{e.inspect}"
64
+ flash[:failure] = t('paypal_error', scope: SCOPE)
65
+ return redirect_to main_app.new_project_contribution_path(contribution.project)
66
+ end
67
+ end
68
+
69
+ def cancel
70
+ flash[:failure] = t('paypal_cancel', scope: SCOPE)
71
+ redirect_to main_app.new_project_contribution_path(contribution.project)
72
+ end
73
+
74
+ def contribution
75
+ @contribution ||= if params['id']
76
+ PaymentEngines.find_payment(id: params['id'])
77
+ elsif params['txn_id']
78
+ PaymentEngines.find_payment(payment_id: params['txn_id']) || (params['parent_txn_id'] && PaymentEngines.find_payment(payment_id: params['parent_txn_id']))
79
+ end
80
+ end
81
+
82
+ def process_paypal_message(data)
83
+ extra_data = (data['charset'] ? JSON.parse(data.to_json.force_encoding(data['charset']).encode('utf-8')) : data)
84
+ PaymentEngines.create_payment_notification contribution_id: contribution.id, extra_data: extra_data
85
+
86
+ if data["checkout_status"] == 'PaymentActionCompleted'
87
+ contribution.confirm!
88
+ elsif data["payment_status"]
89
+ case data["payment_status"].downcase
90
+ when 'completed'
91
+ contribution.confirm!
92
+ when 'refunded'
93
+ contribution.refund!
94
+ when 'canceled_reversal'
95
+ contribution.cancel!
96
+ when 'expired', 'denied'
97
+ contribution.pendent!
98
+ else
99
+ contribution.waiting! if contribution.pending?
100
+ end
101
+ end
102
+ end
103
+
104
+ def gateway
105
+ @gateway ||= CatarsePaypalExpress::Gateway.instance
106
+ end
107
+
108
+ protected
109
+
110
+ def notification
111
+ @notification ||= Paypal::Notification.new(request.raw_post)
112
+ end
113
+ end
@@ -0,0 +1,20 @@
1
+ = javascript_include_tag 'catarse_paypal_express'
2
+
3
+ #catarse_paypal_express_form
4
+ = form_tag pay_paypal_express_path(params[:id]) do
5
+ .w-row
6
+ .w-col.w-col-6
7
+ = label_tag 'user_document', t('projects.contributions.review.international.user_document_label'), class: 'field-label fontweight-semibold'
8
+ = text_field_tag 'user_document', nil, { autocomplete: 'off', class: 'w-input text-field', data: {mask: '999.999.999-99'} }
9
+ .w-row
10
+ .w-col.w-col-12
11
+ .card.card-message.fontsize-small.u-radius.zindex-10.u-marginbottom-30 = t('projects.contributions.review.international.section_title')
12
+ .w-row
13
+ .w-col.w-col-push-3.w-col-6
14
+ .loader.u-text-center.w-col.w-col-12.u-marginbottom-30 = image_tag "catarse_bootstrap/loader.gif"
15
+ = submit_tag t('projects.contributions.review.international.button'), :class => 'btn btn-large u-marginbottom-20'
16
+ .fontsize-smallest.u-text-center
17
+ | Ao apoiar, você concorda com os
18
+ = link_to 'Termos de Uso', main_app.terms_of_use_path, class: 'alt-link'
19
+ | e
20
+ = link_to 'Política de Privacidade', main_app.privacy_policy_path, class: 'alt-link'
@@ -0,0 +1,27 @@
1
+ #encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ # Maintain your gem's version:
5
+ require "catarse_paypal_express/version"
6
+
7
+ # Describe your gem and declare its dependencies:
8
+ Gem::Specification.new do |s|
9
+ s.name = "funddit_paypal_express"
10
+ s.version = CatarsePaypalExpress::VERSION
11
+ s.authors = ["Joshua Chua"]
12
+ s.email = ["funddit@gmail.com"]
13
+ s.homepage = "http://funddit.me"
14
+ s.summary = "PaypalExpress integration with Funddit"
15
+ s.description = "PaypalExpress integration with Funddit crowdfunding platform"
16
+
17
+ s.files = `git ls-files`.split($\)
18
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
19
+
20
+ s.add_dependency "rails", "~> 4.0"
21
+ s.add_dependency "activemerchant", ">= 1.34.0"
22
+ s.add_dependency "slim-rails"
23
+
24
+ s.add_development_dependency "rspec-rails", '~> 2.14.0'
25
+ s.add_development_dependency "factory_girl_rails"
26
+ s.add_development_dependency "database_cleaner"
27
+ end
@@ -0,0 +1,2 @@
1
+ ActiveMerchant::Billing::PaypalExpressGateway.default_currency = (PaymentEngines.configuration[:currency_charge] rescue nil) || 'BRL'
2
+ ActiveMerchant::Billing::Base.mode = :test if (PaymentEngines.configuration[:paypal_test] == 'true' rescue nil)
@@ -0,0 +1,5 @@
1
+ begin
2
+ PaymentEngines.register(CatarsePaypalExpress::PaymentEngine.new)
3
+ rescue Exception => e
4
+ puts "Error while registering payment engine: #{e}"
5
+ end
@@ -0,0 +1,17 @@
1
+ en:
2
+ projects:
3
+ contributions:
4
+ refund:
5
+ success: 'Pedido de reembolso enviado com sucesso!'
6
+ error: 'OPS! Ocorreu um erro ao tentar enviar o pedido de reembolso.'
7
+ review:
8
+ paypal: 'PayPal'
9
+ international:
10
+ section_title: 'You will be directed to the PayPal site to complete payment.'
11
+ user_document_label: 'CPF / CNPJ (only for Brazilians)'
12
+ button: 'Redirect to PayPal'
13
+ checkout:
14
+ paypal_cancel: "Your PayPal payment was canceled. Please try again."
15
+ paypal_description: "Back project %{project_name} with %{value} (BRL)"
16
+ paypal_error: "Ooops. There was an error while sending your payment to PayPal. Please try again."
17
+ success: "Your back was successfully made. Thanks a lot!"
@@ -0,0 +1,17 @@
1
+ pt:
2
+ projects:
3
+ contributions:
4
+ refund:
5
+ success: 'Pedido de reembolso enviado com sucesso!'
6
+ error: 'OPS! Ocorreu um erro ao tentar enviar o pedido de reembolso.'
7
+ review:
8
+ paypal: 'Pagamento internacional - PayPal'
9
+ international:
10
+ section_title: 'Você será direcionado para o site do Paypal para completar o pagamento.'
11
+ user_document_label: 'CPF / CNPJ (somente números) opcional'
12
+ button: 'Ir para o paypal'
13
+ checkout:
14
+ paypal_cancel: "Seu pagamento no PayPal foi cancelado. Por favor, tente novamente."
15
+ paypal_description: "Apoio para o projeto %{project_name} no valor de %{value}"
16
+ paypal_error: "Ooops. Ocorreu um erro ao enviar seu pagamento para o PayPal. Por favor, tente novamente."
17
+ success: "Seu apoio foi realizado com sucesso. Muito obrigado!"
@@ -0,0 +1,15 @@
1
+ CatarsePaypalExpress::Engine.routes.draw do
2
+ resources :paypal_express, only: [], path: 'payment/paypal_express' do
3
+ collection do
4
+ post :ipn
5
+ end
6
+
7
+ member do
8
+ get :review
9
+ post :pay
10
+ get :success
11
+ get :cancel
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,8 @@
1
+ require 'active_merchant'
2
+ require "catarse_paypal_express/engine"
3
+ require "catarse_paypal_express/gateway"
4
+ require "catarse_paypal_express/contribution_actions"
5
+ require "catarse_paypal_express/payment_engine"
6
+
7
+ module CatarsePaypalExpress
8
+ end
@@ -0,0 +1,20 @@
1
+ module CatarsePaypalExpress
2
+ class ContributionActions
3
+
4
+ def initialize contribution
5
+ @contribution = contribution
6
+ end
7
+
8
+ def refund
9
+ refund_request = gateway.refund(nil, @contribution.payment_id)
10
+ refund_request.success?
11
+ end
12
+
13
+ private
14
+
15
+ def gateway
16
+ @gateway ||= CatarsePaypalExpress::Gateway.instance
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module CatarsePaypalExpress
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CatarsePaypalExpress
4
+ end
5
+ end
6
+
@@ -0,0 +1,36 @@
1
+ module CatarsePaypalExpress
2
+ class Gateway
3
+ include ActiveMerchant::Billing::Integrations
4
+
5
+ def initialize configuration = PaymentEngines.configuration
6
+ @configuration = configuration
7
+ end
8
+
9
+ def self.instance
10
+ new.setup_gateway
11
+ rescue Exception => e
12
+ puts e.message
13
+ end
14
+
15
+ def setup_gateway
16
+ check_default_configurations!
17
+ ActiveMerchant::Billing::PaypalExpressGateway.new({
18
+ login: @configuration[:paypal_username],
19
+ password: @configuration[:paypal_password],
20
+ signature: @configuration[:paypal_signature]
21
+ })
22
+ end
23
+
24
+ private
25
+
26
+ def check_default_configurations!
27
+ %i(paypal_username paypal_password paypal_signature).each do |key|
28
+ raise pending_configuration_message unless @configuration[key].present?
29
+ end
30
+ end
31
+
32
+ def pending_configuration_message
33
+ "[PayPal] An API Certificate or API Signature is required to make requests to PayPal"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ module CatarsePaypalExpress
2
+ class PaymentEngine
3
+
4
+ def name
5
+ 'PayPal'
6
+ end
7
+
8
+ def review_path contribution
9
+ CatarsePaypalExpress::Engine.routes.url_helpers.review_paypal_express_path(contribution)
10
+ end
11
+
12
+ def can_do_refund?
13
+ true
14
+ end
15
+
16
+ def direct_refund contribution
17
+ CatarsePaypalExpress::ContributionActions.new(contribution).refund
18
+ end
19
+
20
+ def locale
21
+ 'en'
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module CatarsePaypalExpress
2
+ VERSION = "3.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :catarse_paypal_express do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/catarse_paypal_express/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'
9
+
@@ -0,0 +1,363 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe CatarsePaypalExpress::PaypalExpressController do
6
+ SCOPE = CatarsePaypalExpress::PaypalExpressController::SCOPE
7
+ before do
8
+ PaymentEngines.stub(:find_payment).and_return(contribution)
9
+ PaymentEngines.stub(:create_payment_notification)
10
+ controller.stub(:main_app).and_return(main_app)
11
+ controller.stub(:current_user).and_return(current_user)
12
+ controller.stub(:gateway).and_return(gateway)
13
+ end
14
+
15
+ subject{ response }
16
+ let(:gateway){ double('gateway') }
17
+ let(:main_app){ double('main_app') }
18
+ let(:current_user) { double('current_user') }
19
+ let(:project){ double('project', id: 1, name: 'test project') }
20
+ let(:contribution){ double('contribution', {
21
+ id: 1,
22
+ key: 'contribution key',
23
+ payment_id: 'payment id',
24
+ project: project,
25
+ pending?: true,
26
+ value: 10,
27
+ display_value: 'R$ 10,00',
28
+ price_in_cents: 1000,
29
+ user: current_user,
30
+ payer_name: 'foo',
31
+ payer_email: 'foo@bar.com',
32
+ payment_token: 'token',
33
+ address_street: 'test',
34
+ address_number: '123',
35
+ address_complement: '123',
36
+ address_neighbourhood: '123',
37
+ address_city: '123',
38
+ address_state: '123',
39
+ address_zip_code: '123',
40
+ address_phone_number: '123',
41
+ payment_method: 'PayPal'
42
+ }) }
43
+
44
+ describe "GET review" do
45
+ before do
46
+ get :review, id: contribution.id, use_route: 'catarse_paypal_express'
47
+ end
48
+ it{ should render_template(:review) }
49
+ end
50
+
51
+ describe "POST ipn" do
52
+ let(:ipn_data){ {"mc_gross"=>"50.00", "protection_eligibility"=>"Eligible", "address_status"=>"unconfirmed", "payer_id"=>"S7Q8X88KMGX5S", "tax"=>"0.00", "address_street"=>"Rua Tatui, 40 ap 81\r\nJardins", "payment_date"=>"09:03:01 Nov 05, 2012 PST", "payment_status"=>"Completed", "charset"=>"windows-1252", "address_zip"=>"01409-010", "first_name"=>"Paula", "mc_fee"=>"3.30", "address_country_code"=>"BR", "address_name"=>"Paula Rizzo", "notify_version"=>"3.7", "custom"=>"", "payer_status"=>"verified", "address_country"=>"Brazil", "address_city"=>"Sao Paulo", "quantity"=>"1", "verify_sign"=>"ALBe4QrXe2sJhpq1rIN8JxSbK4RZA.Kfc5JlI9Jk4N1VQVTH5hPYOi2S", "payer_email"=>"paula.rizzo@gmail.com", "txn_id"=>"3R811766V4891372K", "payment_type"=>"instant", "last_name"=>"Rizzo", "address_state"=>"SP", "receiver_email"=>"financeiro@catarse.me", "payment_fee"=>"", "receiver_id"=>"BVUB4EVC7YCWL", "txn_type"=>"express_checkout", "item_name"=>"Back project", "mc_currency"=>"BRL", "item_number"=>"", "residence_country"=>"BR", "handling_amount"=>"0.00", "transaction_subject"=>"Back project", "payment_gross"=>"", "shipping"=>"0.00", "ipn_track_id"=>"5865649c8c27"} }
53
+ let(:contribution){ double(:contribution, :payment_id => ipn_data['txn_id'], :payment_method => 'PayPal' ) }
54
+ let(:notification) { double }
55
+
56
+ before do
57
+ controller.stub(:notification).and_return(notification)
58
+ end
59
+
60
+ context "when payment_method is MoIP" do
61
+ before do
62
+ params = ipn_data.merge({ use_route: 'catarse_paypal_express' })
63
+
64
+ notification.stub(:acknowledge).and_return(true)
65
+ contribution.stub(:payment_method).and_return('MoIP')
66
+
67
+ contribution.should_not_receive(:update_attributes)
68
+ controller.should_not_receive(:process_paypal_message)
69
+
70
+ notification.should_receive(:acknowledge)
71
+
72
+ post :ipn, params
73
+ end
74
+
75
+ its(:status){ should == 500 }
76
+ its(:body){ should == ' ' }
77
+ end
78
+
79
+ context "when is a valid ipn data" do
80
+ before do
81
+ params = ipn_data.merge({ use_route: 'catarse_paypal_express' })
82
+
83
+ notification.stub(:acknowledge).and_return(true)
84
+
85
+ contribution.should_receive(:update_attributes).with({
86
+ payment_service_fee: ipn_data['mc_fee'],
87
+ payer_email: ipn_data['payer_email']
88
+ })
89
+ controller.should_receive(:process_paypal_message).with(ipn_data.merge({
90
+ "controller"=>"catarse_paypal_express/paypal_express",
91
+ "action"=>"ipn"
92
+ }))
93
+
94
+ notification.should_receive(:acknowledge)
95
+
96
+ post :ipn, params
97
+ end
98
+
99
+ its(:status){ should == 200 }
100
+ its(:body){ should == ' ' }
101
+ end
102
+
103
+ context "when is not valid ipn data" do
104
+ let(:ipn_data){ {"mc_gross"=>"50.00", "payment_status" => 'confirmed', "txn_id" => "3R811766V4891372K", 'payer_email' => 'fake@email.com', 'mc_fee' => '0.0'} }
105
+
106
+ before do
107
+ params = ipn_data.merge({ use_route: 'catarse_paypal_express' })
108
+
109
+ notification.stub(:acknowledge).and_return(false)
110
+
111
+ contribution.should_receive(:update_attributes).with({
112
+ payment_service_fee: ipn_data['mc_fee'],
113
+ payer_email: ipn_data['payer_email']
114
+ }).never
115
+
116
+ controller.should_receive(:process_paypal_message).with(ipn_data.merge({
117
+ "controller"=>"catarse_paypal_express/paypal_express",
118
+ "action"=>"ipn"
119
+ })).never
120
+
121
+ notification.should_receive(:acknowledge)
122
+
123
+ post :ipn, params
124
+ end
125
+
126
+ its(:status){ should == 500 }
127
+ its(:body){ should == ' ' }
128
+ end
129
+ end
130
+
131
+ describe "POST pay" do
132
+ before do
133
+ set_paypal_response
134
+ post :pay, { id: contribution.id, locale: 'en', use_route: 'catarse_paypal_express' }
135
+ end
136
+
137
+
138
+ context 'when response raises a exception' do
139
+ let(:set_paypal_response) do
140
+ main_app.should_receive(:new_project_contribution_path).with(contribution.project).and_return('error url')
141
+ gateway.should_receive(:setup_purchase).and_raise(StandardError)
142
+ end
143
+ it 'should assign flash error' do
144
+ controller.flash[:failure].should == I18n.t('paypal_error', scope: SCOPE)
145
+ end
146
+ it{ should redirect_to 'error url' }
147
+ end
148
+
149
+ context 'when successul' do
150
+ let(:set_paypal_response) do
151
+ success_response = double('success_response', {
152
+ token: 'ABCD',
153
+ params: { 'correlation_id' => '123' }
154
+ })
155
+ gateway.should_receive(:setup_purchase).with(
156
+ contribution.price_in_cents,
157
+ {
158
+ ip: request.remote_ip,
159
+ return_url: 'http://test.host/catarse_paypal_express/payment/paypal_express/1/success',
160
+ cancel_return_url: 'http://test.host/catarse_paypal_express/payment/paypal_express/1/cancel',
161
+ currency_code: 'BRL',
162
+ description: I18n.t('paypal_description', scope: SCOPE, :project_name => contribution.project.name, :value => contribution.display_value),
163
+ notify_url: 'http://test.host/catarse_paypal_express/payment/paypal_express/ipn'
164
+ }
165
+ ).and_return(success_response)
166
+ contribution.should_receive(:update_attributes).with({
167
+ payment_method: "PayPal",
168
+ payment_token: "ABCD"
169
+ })
170
+ gateway.should_receive(:redirect_url_for).with('ABCD').and_return('success url')
171
+ end
172
+ it{ should redirect_to 'success url' }
173
+ end
174
+ end
175
+
176
+ describe "GET cancel" do
177
+ before do
178
+ main_app.should_receive(:new_project_contribution_path).with(contribution.project).and_return('new contribution url')
179
+ get :cancel, { id: contribution.id, locale: 'en', use_route: 'catarse_paypal_express' }
180
+ end
181
+ it 'should show for user the flash message' do
182
+ controller.flash[:failure].should == I18n.t('paypal_cancel', scope: SCOPE)
183
+ end
184
+ it{ should redirect_to 'new contribution url' }
185
+ end
186
+
187
+ describe "GET success" do
188
+ let(:success_details){ double('success_details', params: {'transaction_id' => '12345', "checkout_status" => "PaymentActionCompleted"}) }
189
+ let(:params){{ id: contribution.id, PayerID: '123', locale: 'en', use_route: 'catarse_paypal_express' }}
190
+
191
+ before do
192
+ gateway.should_receive(:purchase).with(contribution.price_in_cents, {
193
+ ip: request.remote_ip,
194
+ token: contribution.payment_token,
195
+ payer_id: params[:PayerID]
196
+ }).and_return(success_details)
197
+ controller.should_receive(:process_paypal_message).with(success_details.params)
198
+ contribution.should_receive(:update_attributes).with(payment_id: '12345')
199
+ set_redirect_expectations
200
+ get :success, params
201
+ end
202
+
203
+ context "when purchase is successful" do
204
+ let(:set_redirect_expectations) do
205
+ main_app.
206
+ should_receive(:project_contribution_path).
207
+ with(project_id: contribution.project.id, id: contribution.id).
208
+ and_return('back url')
209
+ end
210
+ it{ should redirect_to 'back url' }
211
+ it 'should assign flash message' do
212
+ controller.flash[:success].should == I18n.t('success', scope: SCOPE)
213
+ end
214
+ end
215
+
216
+ context 'when paypal purchase raises some error' do
217
+ let(:set_redirect_expectations) do
218
+ main_app.
219
+ should_receive(:project_contribution_path).
220
+ with(project_id: contribution.project.id, id: contribution.id).
221
+ and_raise('error')
222
+ main_app.
223
+ should_receive(:new_project_contribution_path).
224
+ with(contribution.project).
225
+ and_return('new back url')
226
+ end
227
+ it 'should assign flash error' do
228
+ controller.flash[:failure].should == I18n.t('paypal_error', scope: SCOPE)
229
+ end
230
+ it{ should redirect_to 'new back url' }
231
+ end
232
+ end
233
+
234
+ describe "#gateway" do
235
+ before do
236
+ controller.stub(:gateway).and_call_original
237
+ PaymentEngines.stub(:configuration).and_return(paypal_config)
238
+ end
239
+ subject{ controller.gateway }
240
+ context "when we have the paypal configuration" do
241
+ let(:paypal_config) do
242
+ { paypal_username: 'username', paypal_password: 'pass', paypal_signature: 'signature' }
243
+ end
244
+ before do
245
+ ActiveMerchant::Billing::PaypalExpressGateway.should_receive(:new).with({
246
+ login: PaymentEngines.configuration[:paypal_username],
247
+ password: PaymentEngines.configuration[:paypal_password],
248
+ signature: PaymentEngines.configuration[:paypal_signature]
249
+ }).and_return('gateway instance')
250
+ end
251
+ it{ should == 'gateway instance' }
252
+ end
253
+
254
+ context "when we do not have the paypal configuration" do
255
+ let(:paypal_config){ {} }
256
+ before do
257
+ ActiveMerchant::Billing::PaypalExpressGateway.should_not_receive(:new)
258
+ end
259
+ it{ should be_nil }
260
+ end
261
+ end
262
+
263
+ describe "#contribution" do
264
+ subject{ controller.contribution }
265
+ context "when we have an id" do
266
+ before do
267
+ controller.stub(:params).and_return({'id' => '1'})
268
+ PaymentEngines.should_receive(:find_payment).with(id: '1').and_return(contribution)
269
+ end
270
+ it{ should == contribution }
271
+ end
272
+
273
+ context "when we have an txn_id that does not return contribution but a parent_txn_id that does" do
274
+ before do
275
+ controller.stub(:params).and_return({'txn_id' => '1', 'parent_txn_id' => '2'})
276
+ PaymentEngines.should_receive(:find_payment).with(payment_id: '1').and_return(nil)
277
+ PaymentEngines.should_receive(:find_payment).with(payment_id: '2').and_return(contribution)
278
+ end
279
+ it{ should == contribution }
280
+ end
281
+
282
+ context "when we do not have any id" do
283
+ before do
284
+ controller.stub(:params).and_return({})
285
+ PaymentEngines.should_not_receive(:find_payment)
286
+ end
287
+ it{ should be_nil }
288
+ end
289
+
290
+ context "when we have an txn_id" do
291
+ before do
292
+ controller.stub(:params).and_return({'txn_id' => '1'})
293
+ PaymentEngines.should_receive(:find_payment).with(payment_id: '1').and_return(contribution)
294
+ end
295
+ it{ should == contribution }
296
+ end
297
+ end
298
+
299
+ describe "#process_paypal_message" do
300
+ subject{ controller.process_paypal_message data }
301
+ let(:data){ {'test_data' => true} }
302
+ before do
303
+ controller.stub(:params).and_return({'id' => 1})
304
+ PaymentEngines.should_receive(:create_payment_notification).with(contribution_id: contribution.id, extra_data: data)
305
+ end
306
+
307
+ context "when data['checkout_status'] == 'PaymentActionCompleted'" do
308
+ let(:data){ {'checkout_status' => 'PaymentActionCompleted'} }
309
+ before do
310
+ contribution.should_receive(:confirm!)
311
+ end
312
+ it("should call confirm"){ subject }
313
+ end
314
+
315
+ context "some real data with revert op" do
316
+ let(:data){ { "mc_gross" => "-150.00","protection_eligibility" => "Eligible","payer_id" => "4DK6S6Q75Z5YS","address_street" => "AV. SAO CARLOS, 2205 - conj 501/502 Centro","payment_date" => "09:55:14 Jun 26, 2013 PDT","payment_status" => "Refunded","charset" => "utf-8","address_zip" => "13560-900","first_name" => "Marcius","mc_fee" => "-8.70","address_country_code" => "BR","address_name" => "Marcius Milori","notify_version" => "3.7","reason_code" => "refund","custom" => "","address_country" => "Brazil","address_city" => "São Carlos","verify_sign" => "AbedXpvDaliC7hltYoQrebkEQft7A.y6bRnDvjPIIB1Mct8-aDGcHkcV","payer_email" => "milorimarcius@gmail.com","parent_txn_id" => "78T862320S496750Y","txn_id" => "9RP43514H84299332","payment_type" => "instant","last_name" => "Milori","address_state" => "São Paulo","receiver_email" => "financeiro@catarse.me","payment_fee" => "","receiver_id" => "BVUB4EVC7YCWL","item_name" => "Apoio para o projeto A Caça (La Chasse) no valor de R$ 150","mc_currency" => "BRL","item_number" => "","residence_country" => "BR","handling_amount" => "0.00","transaction_subject" => "Apoio para o projeto A Caça (La Chasse) no valor de R$ 150","payment_gross" => "","shipping" => "0.00","ipn_track_id" => "18c487e6abca4" } }
317
+ before do
318
+ contribution.should_receive(:refund!)
319
+ end
320
+ it("should call refund"){ subject }
321
+ end
322
+
323
+ context "when it's a refund message" do
324
+ let(:data){ {'payment_status' => 'refunded'} }
325
+ before do
326
+ contribution.should_receive(:refund!)
327
+ end
328
+ it("should call refund"){ subject }
329
+ end
330
+
331
+ context "when it's a completed message" do
332
+ let(:data){ {'payment_status' => 'Completed'} }
333
+ before do
334
+ contribution.should_receive(:confirm!)
335
+ end
336
+ it("should call confirm"){ subject }
337
+ end
338
+
339
+ context "when it's a cancelation message" do
340
+ let(:data){ {'payment_status' => 'canceled_reversal'} }
341
+ before do
342
+ contribution.should_receive(:cancel!)
343
+ end
344
+ it("should call cancel"){ subject }
345
+ end
346
+
347
+ context "when it's a payment expired message" do
348
+ let(:data){ {'payment_status' => 'expired'} }
349
+ before do
350
+ contribution.should_receive(:pendent!)
351
+ end
352
+ it("should call pendent"){ subject }
353
+ end
354
+
355
+ context "all other values of payment_status" do
356
+ let(:data){ {'payment_status' => 'other'} }
357
+ before do
358
+ contribution.should_receive(:waiting!)
359
+ end
360
+ it("should call waiting"){ subject }
361
+ end
362
+ end
363
+ end