funddit_paypal_express 3.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 (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