catarse_paypal_express 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +0 -2
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +2 -126
- data/Gemfile.lock +88 -477
- data/README.md +3 -12
- data/app/assets/javascripts/catarse_paypal_express.js +6 -0
- data/app/assets/javascripts/catarse_paypal_express/paypal_form.js +19 -0
- data/app/assets/javascripts/catarse_paypal_express/user_document.js +111 -0
- data/app/controllers/catarse_paypal_express/paypal_express_controller.rb +113 -0
- data/app/views/catarse_paypal_express/paypal_express/review.html.slim +13 -0
- data/catarse_paypal_express.gemspec +4 -3
- data/config/initializers/active_merchant.rb +2 -0
- data/config/initializers/register.rb +5 -0
- data/config/locales/en.yml +7 -1
- data/config/locales/pt.yml +6 -0
- data/config/routes.rb +11 -5
- data/lib/catarse_paypal_express/version.rb +1 -1
- data/spec/controllers/catarse_paypal_express/paypal_express_controller_spec.rb +304 -0
- data/spec/fixtures/ipn_data.txt +1 -0
- data/spec/spec_helper.rb +12 -26
- data/spec/support/payment_engines.rb +3 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.gitkeep +0 -0
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +58 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +52 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/log/.gitkeep +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- metadata +112 -34
- data/app/controllers/catarse_paypal_express/payment/paypal_express_controller.rb +0 -119
- data/lib/catarse_paypal_express/processors.rb +0 -5
- data/lib/catarse_paypal_express/processors/paypal.rb +0 -26
- data/spec/controllers/catarse_paypal_express/payment/paypal_express_controller_spec.rb +0 -177
- data/spec/lib/processors/paypal_spec.rb +0 -33
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# CatarsePaypalExpress
|
1
|
+
# CatarsePaypalExpress [![Build Status](https://travis-ci.org/catarse/catarse_paypal_express.png)](https://travis-ci.org/catarse/catarse_paypal_express)
|
2
2
|
|
3
3
|
Catarse paypal express integration with [Catarse](http://github.com/danielweinmann/catarse) crowdfunding platform
|
4
4
|
|
@@ -38,22 +38,13 @@ Clone the repository:
|
|
38
38
|
|
39
39
|
Add the catarse code into test/dummy:
|
40
40
|
|
41
|
-
$ git submodule
|
42
|
-
|
43
|
-
Copy the Catarse's gems to Gemfile:
|
44
|
-
|
45
|
-
$ cat test/dummy/Gemfile >> Gemfile
|
41
|
+
$ git submodule init
|
42
|
+
$ git submodule update
|
46
43
|
|
47
44
|
And then execute:
|
48
45
|
|
49
46
|
$ bundle
|
50
47
|
|
51
|
-
## Troubleshooting in development environment
|
52
|
-
|
53
|
-
Remove the admin folder from test/dummy application to prevent a weird active admin bug:
|
54
|
-
|
55
|
-
$ rm -rf test/dummy/app/admin
|
56
|
-
|
57
48
|
## Contributing
|
58
49
|
|
59
50
|
1. Fork it
|
@@ -0,0 +1,19 @@
|
|
1
|
+
App.addChild('PayPalForm', _.extend({
|
2
|
+
el: '#catarse_paypal_express_form',
|
3
|
+
|
4
|
+
events: {
|
5
|
+
'click input[type=submit]': 'onSubmitToPayPal',
|
6
|
+
'keyup #user_document' : 'onUserDocumentKeyup'
|
7
|
+
},
|
8
|
+
|
9
|
+
activate: function() {
|
10
|
+
this.loader = $('.loader');
|
11
|
+
this.parent.backerId = $('input#backer_id').val();
|
12
|
+
this.parent.projectId = $('input#project_id').val();
|
13
|
+
},
|
14
|
+
|
15
|
+
onSubmitToPayPal: function(e) {
|
16
|
+
$(e.currentTarget).hide();
|
17
|
+
this.loader.show();
|
18
|
+
}
|
19
|
+
}, window.PayPal.UserDocument));
|
@@ -0,0 +1,111 @@
|
|
1
|
+
var PayPal = window.PayPal = { UserDocument: {
|
2
|
+
onContentClick: function(e){
|
3
|
+
window.setTimeout(function(){
|
4
|
+
this.moipForm.checkoutSuccessful({'StatusPagamento': 'Success'});
|
5
|
+
}, 2000);
|
6
|
+
},
|
7
|
+
|
8
|
+
onUserDocumentKeyup: function(e){
|
9
|
+
var $documentField = $(e.currentTarget);
|
10
|
+
|
11
|
+
var documentNumber = $documentField.val();
|
12
|
+
$documentField.prop('maxlength', 18);
|
13
|
+
var resultCpf = this.validateCpf(documentNumber);
|
14
|
+
var resultCnpj = this.validateCnpj(documentNumber.replace(/[\/.\-\_ ]/g, ''));
|
15
|
+
var numberLength = documentNumber.replace(/[.\-\_ ]/g, '').length
|
16
|
+
if(numberLength > 10) {
|
17
|
+
if($documentField.attr('id') != 'payment_card_cpf'){
|
18
|
+
if(numberLength == 11) {$documentField.mask('999.999.999-99?999'); }//CPF
|
19
|
+
else if(numberLength == 14 ){$documentField.mask('99.999.999/9999-99');}//CNPJ
|
20
|
+
if(numberLength != 14 || numberLength != 11){ $documentField.unmask()}
|
21
|
+
}
|
22
|
+
|
23
|
+
if(resultCpf || resultCnpj) {
|
24
|
+
$documentField.addClass('ok').removeClass('error');
|
25
|
+
|
26
|
+
$.post('/projects/' + this.parent.projectId + '/backers/' + this.parent.backerId + '/update_info', {
|
27
|
+
backer: { payer_document: documentNumber }
|
28
|
+
});
|
29
|
+
|
30
|
+
} else {
|
31
|
+
$documentField.addClass('error').removeClass('ok');
|
32
|
+
}
|
33
|
+
}
|
34
|
+
else{
|
35
|
+
$documentField.addClass('error').removeClass('ok');
|
36
|
+
}
|
37
|
+
|
38
|
+
},
|
39
|
+
|
40
|
+
validateCpf: function(cpfString){
|
41
|
+
var product = 0, i, digit;
|
42
|
+
cpfString = cpfString.replace(/[.\-\_ ]/g, '');
|
43
|
+
var aux = Math.floor(parseFloat(cpfString) / 100);
|
44
|
+
var cpf = aux * 100;
|
45
|
+
var quotient;
|
46
|
+
|
47
|
+
for(i=0; i<=8; i++){
|
48
|
+
product += (aux % 10) * (i+2);
|
49
|
+
aux = Math.floor(aux / 10);
|
50
|
+
}
|
51
|
+
digit = product % 11 < 2 ? 0 : 11 - (product % 11);
|
52
|
+
cpf += (digit * 10);
|
53
|
+
product = 0;
|
54
|
+
aux = Math.floor(cpf / 10);
|
55
|
+
for(i=0; i<=9; i++){
|
56
|
+
product += (aux % 10) * (i+2);
|
57
|
+
aux = Math.floor(aux / 10);
|
58
|
+
}
|
59
|
+
digit = product % 11 < 2 ? 0 : 11 - (product % 11);
|
60
|
+
cpf += digit;
|
61
|
+
return parseFloat(cpfString) === cpf;
|
62
|
+
},
|
63
|
+
|
64
|
+
validateCnpj: function(cnpj) {
|
65
|
+
var numeros, digitos, soma, i, resultado, pos, tamanho, digitos_iguais;
|
66
|
+
digitos_iguais = 1;
|
67
|
+
if (cnpj.length < 14 && cnpj.length < 15)
|
68
|
+
return false;
|
69
|
+
for (i = 0; i < cnpj.length - 1; i++)
|
70
|
+
if (cnpj.charAt(i) != cnpj.charAt(i + 1))
|
71
|
+
{
|
72
|
+
digitos_iguais = 0;
|
73
|
+
break;
|
74
|
+
}
|
75
|
+
if (!digitos_iguais)
|
76
|
+
{
|
77
|
+
tamanho = cnpj.length - 2
|
78
|
+
numeros = cnpj.substring(0,tamanho);
|
79
|
+
digitos = cnpj.substring(tamanho);
|
80
|
+
soma = 0;
|
81
|
+
pos = tamanho - 7;
|
82
|
+
for (i = tamanho; i >= 1; i--)
|
83
|
+
{
|
84
|
+
soma += numeros.charAt(tamanho - i) * pos--;
|
85
|
+
if (pos < 2)
|
86
|
+
pos = 9;
|
87
|
+
}
|
88
|
+
resultado = soma % 11 < 2 ? 0 : 11 - soma % 11;
|
89
|
+
if (resultado != digitos.charAt(0))
|
90
|
+
return false;
|
91
|
+
tamanho = tamanho + 1;
|
92
|
+
numeros = cnpj.substring(0,tamanho);
|
93
|
+
soma = 0;
|
94
|
+
pos = tamanho - 7;
|
95
|
+
for (i = tamanho; i >= 1; i--)
|
96
|
+
{
|
97
|
+
soma += numeros.charAt(tamanho - i) * pos--;
|
98
|
+
if (pos < 2)
|
99
|
+
pos = 9;
|
100
|
+
}
|
101
|
+
resultado = soma % 11 < 2 ? 0 : 11 - soma % 11;
|
102
|
+
if (resultado != digitos.charAt(1))
|
103
|
+
return false;
|
104
|
+
return true;
|
105
|
+
}
|
106
|
+
else
|
107
|
+
return false;
|
108
|
+
}
|
109
|
+
}};
|
110
|
+
|
111
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class CatarsePaypalExpress::PaypalExpressController < ApplicationController
|
2
|
+
skip_before_filter :force_http
|
3
|
+
SCOPE = "projects.backers.checkout"
|
4
|
+
layout :false
|
5
|
+
|
6
|
+
def review
|
7
|
+
end
|
8
|
+
|
9
|
+
def ipn
|
10
|
+
if backer
|
11
|
+
process_paypal_message params
|
12
|
+
backer.update_attributes({
|
13
|
+
:payment_service_fee => params['mc_fee'],
|
14
|
+
:payer_email => params['payer_email']
|
15
|
+
})
|
16
|
+
else
|
17
|
+
return render status: 500, text: e.inspect
|
18
|
+
end
|
19
|
+
return render status: 200, nothing: true
|
20
|
+
rescue Exception => e
|
21
|
+
return render status: 500, text: e.inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
def pay
|
25
|
+
begin
|
26
|
+
response = gateway.setup_purchase(backer.price_in_cents, {
|
27
|
+
ip: request.remote_ip,
|
28
|
+
return_url: success_paypal_expres_url(id: backer.id),
|
29
|
+
cancel_return_url: cancel_paypal_expres_url(id: backer.id),
|
30
|
+
currency_code: 'BRL',
|
31
|
+
description: t('paypal_description', scope: SCOPE, :project_name => backer.project.name, :value => backer.display_value),
|
32
|
+
notify_url: ipn_paypal_express_url
|
33
|
+
})
|
34
|
+
|
35
|
+
process_paypal_message response.params
|
36
|
+
backer.update_attributes payment_method: 'PayPal', payment_token: response.token
|
37
|
+
|
38
|
+
redirect_to gateway.redirect_url_for(response.token)
|
39
|
+
rescue Exception => e
|
40
|
+
Rails.logger.info "-----> #{e.inspect}"
|
41
|
+
flash[:failure] = t('paypal_error', scope: SCOPE)
|
42
|
+
return redirect_to main_app.new_project_backer_path(backer.project)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def success
|
47
|
+
begin
|
48
|
+
purchase = gateway.purchase(backer.price_in_cents, {
|
49
|
+
ip: request.remote_ip,
|
50
|
+
token: backer.payment_token,
|
51
|
+
payer_id: params[:PayerID]
|
52
|
+
})
|
53
|
+
|
54
|
+
# we must get the deatils after the purchase in order to get the transaction_id
|
55
|
+
process_paypal_message purchase.params
|
56
|
+
backer.update_attributes payment_id: purchase.params['transaction_id'] if purchase.params['transaction_id']
|
57
|
+
|
58
|
+
flash[:success] = t('success', scope: SCOPE)
|
59
|
+
redirect_to main_app.project_backer_path(project_id: backer.project.id, id: backer.id)
|
60
|
+
rescue Exception => e
|
61
|
+
Rails.logger.info "-----> #{e.inspect}"
|
62
|
+
flash[:failure] = t('paypal_error', scope: SCOPE)
|
63
|
+
return redirect_to main_app.new_project_backer_path(backer.project)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def cancel
|
68
|
+
flash[:failure] = t('paypal_cancel', scope: SCOPE)
|
69
|
+
redirect_to main_app.new_project_backer_path(backer.project)
|
70
|
+
end
|
71
|
+
|
72
|
+
def backer
|
73
|
+
@backer ||= if params['id']
|
74
|
+
PaymentEngines.find_payment(id: params['id'])
|
75
|
+
elsif params['txn_id']
|
76
|
+
PaymentEngines.find_payment(payment_id: params['txn_id']) || (params['parent_txn_id'] && PaymentEngines.find_payment(payment_id: params['parent_txn_id']))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def process_paypal_message(data)
|
81
|
+
extra_data = (data['charset'] ? JSON.parse(data.to_json.force_encoding(data['charset']).encode('utf-8')) : data)
|
82
|
+
PaymentEngines.create_payment_notification backer_id: backer.id, extra_data: extra_data
|
83
|
+
|
84
|
+
if data["checkout_status"] == 'PaymentActionCompleted'
|
85
|
+
backer.confirm!
|
86
|
+
elsif data["payment_status"]
|
87
|
+
case data["payment_status"].downcase
|
88
|
+
when 'completed'
|
89
|
+
backer.confirm!
|
90
|
+
when 'refunded'
|
91
|
+
backer.refund!
|
92
|
+
when 'canceled_reversal'
|
93
|
+
backer.cancel!
|
94
|
+
when 'expired', 'denied'
|
95
|
+
backer.pendent!
|
96
|
+
else
|
97
|
+
backer.waiting! if backer.pending?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def gateway
|
103
|
+
if PaymentEngines.configuration[:paypal_username] and PaymentEngines.configuration[:paypal_password] and PaymentEngines.configuration[:paypal_signature]
|
104
|
+
@gateway ||= ActiveMerchant::Billing::PaypalExpressGateway.new({
|
105
|
+
login: PaymentEngines.configuration[:paypal_username],
|
106
|
+
password: PaymentEngines.configuration[:paypal_password],
|
107
|
+
signature: PaymentEngines.configuration[:paypal_signature]
|
108
|
+
})
|
109
|
+
else
|
110
|
+
puts "[PayPal] An API Certificate or API Signature is required to make requests to PayPal"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
= javascript_include_tag 'catarse_paypal_express'
|
2
|
+
|
3
|
+
h3= t('projects.backers.review.international.section_title')
|
4
|
+
|
5
|
+
#catarse_paypal_express_form
|
6
|
+
= form_tag pay_paypal_expres_path(params[:id]) do
|
7
|
+
.clearfix
|
8
|
+
.bootstrap-twitter
|
9
|
+
= label_tag 'user_document', t('projects.backers.review.international.user_document_label')
|
10
|
+
.clearfix
|
11
|
+
= text_field_tag 'user_document', nil, { autocomplete: 'off' }
|
12
|
+
.loader.hide= image_tag 'loading.gif'
|
13
|
+
= submit_tag t('projects.backers.review.international.button'), :class => 'btn btn-primary btn-large'
|
@@ -8,8 +8,8 @@ require "catarse_paypal_express/version"
|
|
8
8
|
Gem::Specification.new do |s|
|
9
9
|
s.name = "catarse_paypal_express"
|
10
10
|
s.version = CatarsePaypalExpress::VERSION
|
11
|
-
s.authors = ["Antônio Roberto Silva"]
|
12
|
-
s.email = ["forevertonny@gmail.com"]
|
11
|
+
s.authors = ["Antônio Roberto Silva", "Diogo Biazus", "Josemar Davi Luedke"]
|
12
|
+
s.email = ["forevertonny@gmail.com", "diogob@gmail.com", "josemarluedke@gmail.com"]
|
13
13
|
s.homepage = "http://github.com/devton/catarse_paypal_express"
|
14
14
|
s.summary = "PaypalExpress integration with Catarse"
|
15
15
|
s.description = "PaypalExpress integration with Catarse crowdfunding platform"
|
@@ -18,7 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
19
|
|
20
20
|
s.add_dependency "rails", "~> 3.2.6"
|
21
|
-
s.add_dependency "activemerchant", "
|
21
|
+
s.add_dependency "activemerchant", ">= 1.17.0"
|
22
|
+
s.add_dependency "slim-rails"
|
22
23
|
|
23
24
|
s.add_development_dependency "rspec-rails"
|
24
25
|
s.add_development_dependency "factory_girl_rails"
|
data/config/locales/en.yml
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
en:
|
2
2
|
projects:
|
3
3
|
backers:
|
4
|
+
review:
|
5
|
+
paypal: 'PayPal'
|
6
|
+
international:
|
7
|
+
section_title: 'You will be directed to the PayPal site to complete payment.'
|
8
|
+
user_document_label: 'CPF / CNPJ (only for Brazilians)'
|
9
|
+
button: 'Redirect to PayPal'
|
4
10
|
checkout:
|
5
11
|
paypal_cancel: "Your PayPal payment was canceled. Please try again."
|
6
|
-
paypal_description: "Back project"
|
12
|
+
paypal_description: "Back project %{project_name} with %{value} (BRL)"
|
7
13
|
paypal_error: "Ooops. There was an error while sending your payment to PayPal. Please try again."
|
8
14
|
success: "Your back was successfully made. Thanks a lot!"
|
data/config/locales/pt.yml
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
pt:
|
2
2
|
projects:
|
3
3
|
backers:
|
4
|
+
review:
|
5
|
+
paypal: 'Pagamento internacional - PayPal'
|
6
|
+
international:
|
7
|
+
section_title: 'Você será direcionado para o site do Paypal para completar o pagamento.'
|
8
|
+
user_document_label: 'CPF / CNPJ (somente números) opcional'
|
9
|
+
button: 'Ir para o paypal'
|
4
10
|
checkout:
|
5
11
|
paypal_cancel: "Seu pagamento no PayPal foi cancelado. Por favor, tente novamente."
|
6
12
|
paypal_description: "Apoio para o projeto %{project_name} no valor de %{value}"
|
data/config/routes.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
CatarsePaypalExpress::Engine.routes.draw do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
+
match :pay
|
10
|
+
match :success
|
11
|
+
match :cancel
|
12
|
+
end
|
7
13
|
end
|
8
14
|
end
|
9
15
|
|
@@ -0,0 +1,304 @@
|
|
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(backer)
|
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(:backer){ double('backer', {
|
21
|
+
id: 1,
|
22
|
+
key: 'backer 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
|
+
}) }
|
42
|
+
|
43
|
+
describe "GET review" do
|
44
|
+
before do
|
45
|
+
get :review, id: backer.id, use_route: 'catarse_paypal_express'
|
46
|
+
end
|
47
|
+
it{ should render_template(:review) }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "POST ipn" do
|
51
|
+
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"} }
|
52
|
+
|
53
|
+
let(:backer){ double(:backer, :payment_id => ipn_data['txn_id'] ) }
|
54
|
+
|
55
|
+
before do
|
56
|
+
params = ipn_data.merge({ use_route: 'catarse_paypal_express' })
|
57
|
+
backer.should_receive(:update_attributes).with({
|
58
|
+
payment_service_fee: ipn_data['mc_fee'],
|
59
|
+
payer_email: ipn_data['payer_email']
|
60
|
+
})
|
61
|
+
controller.should_receive(:process_paypal_message).with(ipn_data.merge({
|
62
|
+
"controller"=>"catarse_paypal_express/paypal_express",
|
63
|
+
"action"=>"ipn"
|
64
|
+
}))
|
65
|
+
post :ipn, params
|
66
|
+
end
|
67
|
+
|
68
|
+
its(:status){ should == 200 }
|
69
|
+
its(:body){ should == ' ' }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "GET pay" do
|
73
|
+
before do
|
74
|
+
set_paypal_response
|
75
|
+
get :pay, { id: backer.id, locale: 'en', use_route: 'catarse_paypal_express' }
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
context 'when response raises a exception' do
|
80
|
+
let(:set_paypal_response) do
|
81
|
+
main_app.should_receive(:new_project_backer_path).with(backer.project).and_return('error url')
|
82
|
+
gateway.should_receive(:setup_purchase).and_raise(StandardError)
|
83
|
+
end
|
84
|
+
it 'should assign flash error' do
|
85
|
+
controller.flash[:failure].should == I18n.t('paypal_error', scope: SCOPE)
|
86
|
+
end
|
87
|
+
it{ should redirect_to 'error url' }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when successul' do
|
91
|
+
let(:set_paypal_response) do
|
92
|
+
success_response = double('success_response', {
|
93
|
+
token: 'ABCD',
|
94
|
+
params: { 'correlation_id' => '123' }
|
95
|
+
})
|
96
|
+
gateway.should_receive(:setup_purchase).with(
|
97
|
+
backer.price_in_cents,
|
98
|
+
{
|
99
|
+
ip: request.remote_ip,
|
100
|
+
return_url: 'http://test.host/catarse_paypal_express/payment/paypal_express/1/success',
|
101
|
+
cancel_return_url: 'http://test.host/catarse_paypal_express/payment/paypal_express/1/cancel',
|
102
|
+
currency_code: 'BRL',
|
103
|
+
description: I18n.t('paypal_description', scope: SCOPE, :project_name => backer.project.name, :value => backer.display_value),
|
104
|
+
notify_url: 'http://test.host/catarse_paypal_express/payment/paypal_express/ipn'
|
105
|
+
}
|
106
|
+
).and_return(success_response)
|
107
|
+
backer.should_receive(:update_attributes).with({
|
108
|
+
payment_method: "PayPal",
|
109
|
+
payment_token: "ABCD"
|
110
|
+
})
|
111
|
+
gateway.should_receive(:redirect_url_for).with('ABCD').and_return('success url')
|
112
|
+
end
|
113
|
+
it{ should redirect_to 'success url' }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "GET cancel" do
|
118
|
+
before do
|
119
|
+
main_app.should_receive(:new_project_backer_path).with(backer.project).and_return('new backer url')
|
120
|
+
get :cancel, { id: backer.id, locale: 'en', use_route: 'catarse_paypal_express' }
|
121
|
+
end
|
122
|
+
it 'should show for user the flash message' do
|
123
|
+
controller.flash[:failure].should == I18n.t('paypal_cancel', scope: SCOPE)
|
124
|
+
end
|
125
|
+
it{ should redirect_to 'new backer url' }
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "GET success" do
|
129
|
+
let(:success_details){ double('success_details', params: {'transaction_id' => '12345', "checkout_status" => "PaymentActionCompleted"}) }
|
130
|
+
let(:params){{ id: backer.id, PayerID: '123', locale: 'en', use_route: 'catarse_paypal_express' }}
|
131
|
+
|
132
|
+
before do
|
133
|
+
gateway.should_receive(:purchase).with(backer.price_in_cents, {
|
134
|
+
ip: request.remote_ip,
|
135
|
+
token: backer.payment_token,
|
136
|
+
payer_id: params[:PayerID]
|
137
|
+
}).and_return(success_details)
|
138
|
+
controller.should_receive(:process_paypal_message).with(success_details.params)
|
139
|
+
backer.should_receive(:update_attributes).with(payment_id: '12345')
|
140
|
+
set_redirect_expectations
|
141
|
+
get :success, params
|
142
|
+
end
|
143
|
+
|
144
|
+
context "when purchase is successful" do
|
145
|
+
let(:set_redirect_expectations) do
|
146
|
+
main_app.
|
147
|
+
should_receive(:project_backer_path).
|
148
|
+
with(project_id: backer.project.id, id: backer.id).
|
149
|
+
and_return('back url')
|
150
|
+
end
|
151
|
+
it{ should redirect_to 'back url' }
|
152
|
+
it 'should assign flash message' do
|
153
|
+
controller.flash[:success].should == I18n.t('success', scope: SCOPE)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when paypal purchase raises some error' do
|
158
|
+
let(:set_redirect_expectations) do
|
159
|
+
main_app.
|
160
|
+
should_receive(:project_backer_path).
|
161
|
+
with(project_id: backer.project.id, id: backer.id).
|
162
|
+
and_raise('error')
|
163
|
+
main_app.
|
164
|
+
should_receive(:new_project_backer_path).
|
165
|
+
with(backer.project).
|
166
|
+
and_return('new back url')
|
167
|
+
end
|
168
|
+
it 'should assign flash error' do
|
169
|
+
controller.flash[:failure].should == I18n.t('paypal_error', scope: SCOPE)
|
170
|
+
end
|
171
|
+
it{ should redirect_to 'new back url' }
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "#gateway" do
|
176
|
+
before do
|
177
|
+
controller.stub(:gateway).and_call_original
|
178
|
+
PaymentEngines.stub(:configuration).and_return(paypal_config)
|
179
|
+
end
|
180
|
+
subject{ controller.gateway }
|
181
|
+
context "when we have the paypal configuration" do
|
182
|
+
let(:paypal_config) do
|
183
|
+
{ paypal_username: 'username', paypal_password: 'pass', paypal_signature: 'signature' }
|
184
|
+
end
|
185
|
+
before do
|
186
|
+
ActiveMerchant::Billing::PaypalExpressGateway.should_receive(:new).with({
|
187
|
+
login: PaymentEngines.configuration[:paypal_username],
|
188
|
+
password: PaymentEngines.configuration[:paypal_password],
|
189
|
+
signature: PaymentEngines.configuration[:paypal_signature]
|
190
|
+
}).and_return('gateway instance')
|
191
|
+
end
|
192
|
+
it{ should == 'gateway instance' }
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when we do not have the paypal configuration" do
|
196
|
+
let(:paypal_config){ {} }
|
197
|
+
before do
|
198
|
+
ActiveMerchant::Billing::PaypalExpressGateway.should_not_receive(:new)
|
199
|
+
end
|
200
|
+
it{ should be_nil }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "#backer" do
|
205
|
+
subject{ controller.backer }
|
206
|
+
context "when we have an id" do
|
207
|
+
before do
|
208
|
+
controller.stub(:params).and_return({'id' => '1'})
|
209
|
+
PaymentEngines.should_receive(:find_payment).with(id: '1').and_return(backer)
|
210
|
+
end
|
211
|
+
it{ should == backer }
|
212
|
+
end
|
213
|
+
|
214
|
+
context "when we have an txn_id that does not return backer but a parent_txn_id that does" do
|
215
|
+
before do
|
216
|
+
controller.stub(:params).and_return({'txn_id' => '1', 'parent_txn_id' => '2'})
|
217
|
+
PaymentEngines.should_receive(:find_payment).with(payment_id: '1').and_return(nil)
|
218
|
+
PaymentEngines.should_receive(:find_payment).with(payment_id: '2').and_return(backer)
|
219
|
+
end
|
220
|
+
it{ should == backer }
|
221
|
+
end
|
222
|
+
|
223
|
+
context "when we do not have any id" do
|
224
|
+
before do
|
225
|
+
controller.stub(:params).and_return({})
|
226
|
+
PaymentEngines.should_not_receive(:find_payment)
|
227
|
+
end
|
228
|
+
it{ should be_nil }
|
229
|
+
end
|
230
|
+
|
231
|
+
context "when we have an txn_id" do
|
232
|
+
before do
|
233
|
+
controller.stub(:params).and_return({'txn_id' => '1'})
|
234
|
+
PaymentEngines.should_receive(:find_payment).with(payment_id: '1').and_return(backer)
|
235
|
+
end
|
236
|
+
it{ should == backer }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "#process_paypal_message" do
|
241
|
+
subject{ controller.process_paypal_message data }
|
242
|
+
let(:data){ {'test_data' => true} }
|
243
|
+
before do
|
244
|
+
controller.stub(:params).and_return({'id' => 1})
|
245
|
+
PaymentEngines.should_receive(:create_payment_notification).with(backer_id: backer.id, extra_data: data)
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when data['checkout_status'] == 'PaymentActionCompleted'" do
|
249
|
+
let(:data){ {'checkout_status' => 'PaymentActionCompleted'} }
|
250
|
+
before do
|
251
|
+
backer.should_receive(:confirm!)
|
252
|
+
end
|
253
|
+
it("should call confirm"){ subject }
|
254
|
+
end
|
255
|
+
|
256
|
+
context "some real data with revert op" do
|
257
|
+
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" } }
|
258
|
+
before do
|
259
|
+
backer.should_receive(:refund!)
|
260
|
+
end
|
261
|
+
it("should call refund"){ subject }
|
262
|
+
end
|
263
|
+
|
264
|
+
context "when it's a refund message" do
|
265
|
+
let(:data){ {'payment_status' => 'refunded'} }
|
266
|
+
before do
|
267
|
+
backer.should_receive(:refund!)
|
268
|
+
end
|
269
|
+
it("should call refund"){ subject }
|
270
|
+
end
|
271
|
+
|
272
|
+
context "when it's a completed message" do
|
273
|
+
let(:data){ {'payment_status' => 'Completed'} }
|
274
|
+
before do
|
275
|
+
backer.should_receive(:confirm!)
|
276
|
+
end
|
277
|
+
it("should call confirm"){ subject }
|
278
|
+
end
|
279
|
+
|
280
|
+
context "when it's a cancelation message" do
|
281
|
+
let(:data){ {'payment_status' => 'canceled_reversal'} }
|
282
|
+
before do
|
283
|
+
backer.should_receive(:cancel!)
|
284
|
+
end
|
285
|
+
it("should call cancel"){ subject }
|
286
|
+
end
|
287
|
+
|
288
|
+
context "when it's a payment expired message" do
|
289
|
+
let(:data){ {'payment_status' => 'expired'} }
|
290
|
+
before do
|
291
|
+
backer.should_receive(:pendent!)
|
292
|
+
end
|
293
|
+
it("should call pendent"){ subject }
|
294
|
+
end
|
295
|
+
|
296
|
+
context "all other values of payment_status" do
|
297
|
+
let(:data){ {'payment_status' => 'other'} }
|
298
|
+
before do
|
299
|
+
backer.should_receive(:waiting!)
|
300
|
+
end
|
301
|
+
it("should call waiting"){ subject }
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|