webpay_rails 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +14 -0
- data/MIT-LICENSE +20 -0
- data/README.md +110 -0
- data/Rakefile +14 -0
- data/lib/webpay_rails/base.rb +70 -0
- data/lib/webpay_rails/errors.rb +9 -0
- data/lib/webpay_rails/soap.rb +101 -0
- data/lib/webpay_rails/transaction.rb +14 -0
- data/lib/webpay_rails/transaction_result.rb +25 -0
- data/lib/webpay_rails/verifier.rb +52 -0
- data/lib/webpay_rails/version.rb +3 -0
- data/lib/webpay_rails.rb +25 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/webpay_rails_spec.rb +123 -0
- data/webpay_rails.gemspec +32 -0
- metadata +183 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0f99e80e015c4b491c9eeb739af651a34b0014b6
|
4
|
+
data.tar.gz: 5eb044ebfea6491227fb5ed7d13bbc2e5e0cf2bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5e02cfd6ec1c2679d56a24d669d2e10893dd2c23be84264d32d33b11c957c2c001b9715fcbca99f5a685c7282bd3090e2a226e0b43325bbc20b5845bc45195c
|
7
|
+
data.tar.gz: 7876ac419e68e480a9b6df2b180f62d2ba02bde7af1fe85936e16625e2fda0601ed21f5a62f011c731c467a355eaa430f9c3e8fe8970602e92cd9c517206e3b7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in secret_id.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
10
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
+
# your gem to rubygems.org.
|
12
|
+
|
13
|
+
# To use a debugger
|
14
|
+
# gem 'byebug', group: [:development, :test]
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Sebastián Orellana
|
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 PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL 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.
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# WebpayRails
|
2
|
+
|
3
|
+
[](https://travis-ci.org/limcross/webpay_rails)
|
4
|
+
[](https://codeclimate.com/github/limcross/webpay_rails)
|
5
|
+
|
6
|
+
WebpayRails is an easy solution for integrate Transbank Webpay in Rails applications.
|
7
|
+
|
8
|
+
## Getting started
|
9
|
+
You can add it to your `Gemfile`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'webpay_rails'
|
13
|
+
```
|
14
|
+
|
15
|
+
Run the bundle command to install it.
|
16
|
+
|
17
|
+
### Configuring models
|
18
|
+
After that, extend the model to `WebpayRails` and add `webpay_rails` to this, like below.
|
19
|
+
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Order < ActiveRecord::Base
|
23
|
+
extend WebpayRails
|
24
|
+
|
25
|
+
webpay_rails({
|
26
|
+
commerce_code: 123456789,
|
27
|
+
private_key: '-----BEGIN RSA PRIVATE KEY-----
|
28
|
+
...
|
29
|
+
-----END RSA PRIVATE KEY-----',
|
30
|
+
public_cert: '-----BEGIN CERTIFICATE-----
|
31
|
+
...
|
32
|
+
-----END CERTIFICATE-----',
|
33
|
+
webpay_cert: '-----BEGIN CERTIFICATE-----
|
34
|
+
...
|
35
|
+
-----END CERTIFICATE-----',
|
36
|
+
environment: :integration
|
37
|
+
})
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
Obviously all these values should not be defined directly in the model. It is strongly recommended to use environment variables for this ([dotenv](https://github.com/bkeepers/dotenv)).
|
42
|
+
|
43
|
+
### Using WebpayRails
|
44
|
+
|
45
|
+
#### Initializing a transaction
|
46
|
+
|
47
|
+
First we need to initialize an transaction, like below:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
@transaction = Order.init_transaction(amount, buy_order, session_id, return_url, final_url)
|
51
|
+
```
|
52
|
+
|
53
|
+
Where `amount` is an __integer__ that define the amount of the transaction (_obviously_), `buy_order` is an __intenger__ that define the order number of the buy, `session_id` is an __string__ that define a local variable that will be returned as part of the result of the transaction, `return_url` and `final_url` are a __string__ for the redirections.
|
54
|
+
|
55
|
+
This method return a `Transaction` object, that contain a redirection `url` and `token` for redirect the customer through POST method, like below.
|
56
|
+
|
57
|
+
```erb
|
58
|
+
<% if @transaction.success? %>
|
59
|
+
<%= form_tag(@transaction.url, method: "post") do %>
|
60
|
+
<%= hidden_field_tag(:token_ws, @transaction.token) %>
|
61
|
+
<%= submit_tag("Pagar con Webpay")
|
62
|
+
<%= end %>
|
63
|
+
<% end %>
|
64
|
+
```
|
65
|
+
|
66
|
+
Once Webpay displays the form of payment and authorization of the bank, the customer will send back through __POST__ method to the `return_url` with a `token_ws`. (If the customer cancels the transaction is directly returned to the `final_url` in the same way).
|
67
|
+
|
68
|
+
#### Getting the result of a transaction
|
69
|
+
|
70
|
+
When Webpay send a __POST__ to `return_url` with `token_ws`, we need to ask for the transaction result, like below.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
@result = Order.transaction_result(params[:token_ws])
|
74
|
+
```
|
75
|
+
|
76
|
+
This method return a `TransactionResult` object, that contain an `accounting_date`, `buy_order`, `card_number`, `amount`, `commerce_code`, `authorization_code`, `payment_type_code`, `response_code`, `transaction_date`, `url_redirection` and `vci`.
|
77
|
+
|
78
|
+
At this point we have confirmed the transaction with Transbank, performing the operation `acknowledge_transaction` by means of `transaction_result`.
|
79
|
+
|
80
|
+
Now we need to send back the customer to `url_redirection` with `token_ws` in the same way we did earlier in the initialization of the transaction.
|
81
|
+
|
82
|
+
#### Ending a transaction
|
83
|
+
|
84
|
+
When Webpay send customer to `final_url`, we are done. Finally the transaction has ended. :clap:
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
Any contribution is welcome. Personally I prefer to use English to do documentation and describe commits, however there is no problem if you make your comments and issues in Spanish.
|
88
|
+
|
89
|
+
### Reporting issues
|
90
|
+
|
91
|
+
Please try to answer the following questions in your bug report:
|
92
|
+
|
93
|
+
- What did you do?
|
94
|
+
- What did you expect to happen?
|
95
|
+
- What happened instead?
|
96
|
+
|
97
|
+
Make sure to include as much relevant information as possible. Ruby version,
|
98
|
+
WebpayRails version, OS version and any stack traces you have are very valuable.
|
99
|
+
|
100
|
+
### Pull Requests
|
101
|
+
|
102
|
+
- __Add tests!__ Your patch won't be accepted if it doesn't have tests.
|
103
|
+
|
104
|
+
- __Document any change in behaviour__. Make sure the README and any relevant documentation are kept up-to-date.
|
105
|
+
|
106
|
+
- __Create topic branches__. Please don't ask us to pull from your master branch.
|
107
|
+
|
108
|
+
- __One pull request per feature__. If you want to do more than one thing, send multiple pull requests.
|
109
|
+
|
110
|
+
- __Send coherent history__. Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before sending them to us.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require "rspec/core/rake_task"
|
8
|
+
|
9
|
+
desc "Run all examples"
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
11
|
+
t.rspec_opts = %w[--color]
|
12
|
+
end
|
13
|
+
|
14
|
+
task default: :spec
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module WebpayRails
|
2
|
+
module Base
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def webpay_rails(options)
|
7
|
+
class_attribute :commerce_code, :webpay_cert, :environment, :soap, instance_accessor: false
|
8
|
+
|
9
|
+
self.commerce_code = options[:commerce_code]
|
10
|
+
self.webpay_cert = OpenSSL::X509::Certificate.new(options[:webpay_cert])
|
11
|
+
self.environment = options[:environment]
|
12
|
+
|
13
|
+
self.soap = WebpayRails::Soap.new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def init_transaction(amount, buy_order, session_id, return_url, final_url)
|
17
|
+
begin
|
18
|
+
response = soap.init_transaction(commerce_code, amount, buy_order, session_id, return_url, final_url)
|
19
|
+
rescue StandardError
|
20
|
+
raise WebpayRails::FailedInitTransaction
|
21
|
+
end
|
22
|
+
|
23
|
+
raise WebpayRails::InvalidCertificate unless WebpayRails::Verifier.verify(response, webpay_cert)
|
24
|
+
|
25
|
+
document = Nokogiri::HTML(response.to_s)
|
26
|
+
WebpayRails::Transaction.new({
|
27
|
+
token: document.at_xpath('//token').text.to_s,
|
28
|
+
url: document.at_xpath('//url').text.to_s
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
def transaction_result(token)
|
33
|
+
begin
|
34
|
+
response = soap.get_transaction_result(token)
|
35
|
+
rescue StandardError
|
36
|
+
raise WebpayRails::FailedGetResult
|
37
|
+
end
|
38
|
+
|
39
|
+
raise WebpayRails::InvalidResultResponse unless response
|
40
|
+
|
41
|
+
acknowledge_transaction(token)
|
42
|
+
|
43
|
+
document = Nokogiri::HTML(response.to_s)
|
44
|
+
WebpayRails::TransactionResult.new({
|
45
|
+
accounting_date: document.at_xpath('//accountingdate').text.to_s,
|
46
|
+
buy_order: document.at_xpath('//buyorder').text.to_s,
|
47
|
+
card_number: document.at_xpath('//cardnumber').text.to_s,
|
48
|
+
amount: document.at_xpath('//amount').text.to_s,
|
49
|
+
commerce_code: document.at_xpath('//commercecode').text.to_s,
|
50
|
+
authorization_code: document.at_xpath('//authorizationcode').text.to_s,
|
51
|
+
payment_type_code: document.at_xpath('//paymenttypecode').text.to_s,
|
52
|
+
response_code: document.at_xpath('//responsecode').text.to_s,
|
53
|
+
transaction_date: document.at_xpath('//transactiondate').text.to_s,
|
54
|
+
url_redirection: document.at_xpath('//urlredirection').text.to_s,
|
55
|
+
vci: document.at_xpath('//vci').text.to_s
|
56
|
+
})
|
57
|
+
end
|
58
|
+
|
59
|
+
def acknowledge_transaction(token)
|
60
|
+
begin
|
61
|
+
response = soap.acknowledge_transaction(token)
|
62
|
+
rescue StandardError
|
63
|
+
raise WebpayRails::FailedAcknowledgeTransaction
|
64
|
+
end
|
65
|
+
|
66
|
+
raise WebpayRails::InvalidAcknowledgeResponse unless response
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module WebpayRails
|
2
|
+
class Error < StandardError; end
|
3
|
+
class FailedInitTransaction < Error; end
|
4
|
+
class FailedGetResult < Error; end
|
5
|
+
class InvalidResultResponse < Error; end
|
6
|
+
class FailedAcknowledgeTransaction < Error; end
|
7
|
+
class InvalidAcknowledgeResponse < Error; end
|
8
|
+
class InvalidEnvironment < Error; end
|
9
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module WebpayRails
|
2
|
+
class Soap
|
3
|
+
extend Savon::Model
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
@private_key = OpenSSL::PKey::RSA.new(args[:private_key])
|
7
|
+
@public_cert = OpenSSL::X509::Certificate.new(args[:public_cert])
|
8
|
+
@environment = args[:environment]
|
9
|
+
|
10
|
+
self.class.client(wsdl: wsdl_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def init_transaction(commerce_code, amount, buy_order, session_id, return_url, final_url)
|
14
|
+
request = client.build_request(:init_transaction, message: {
|
15
|
+
wsInitTransactionInput: {
|
16
|
+
wSTransactionType: 'TR_NORMAL_WS',
|
17
|
+
buyOrder: buy_order,
|
18
|
+
sessionId: session_id,
|
19
|
+
returnURL: return_url,
|
20
|
+
finalURL: final_url,
|
21
|
+
transactionDetails: {
|
22
|
+
amount: amount,
|
23
|
+
commerceCode: commerce_code,
|
24
|
+
buyOrder: buy_order
|
25
|
+
}
|
26
|
+
}
|
27
|
+
})
|
28
|
+
|
29
|
+
call(request, :init_transaction)
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_transaction_result(token)
|
33
|
+
request = client.build_request(:get_transaction_result, message: {
|
34
|
+
tokenInput: token
|
35
|
+
})
|
36
|
+
|
37
|
+
call(request, :get_transaction_result)
|
38
|
+
end
|
39
|
+
|
40
|
+
def acknowledge_transaction(token)
|
41
|
+
request = client.build_request(:acknowledge_transaction, message: {
|
42
|
+
tokenInput: token
|
43
|
+
})
|
44
|
+
|
45
|
+
call(request, :acknowledge_transaction)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def call(request, operation)
|
51
|
+
signed_document = sign_xml(request)
|
52
|
+
|
53
|
+
client.call(operation) do
|
54
|
+
xml signed_document.to_xml(save_with: 0)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def sign_xml(input_xml)
|
59
|
+
document = Nokogiri::XML(input_xml.body)
|
60
|
+
envelope = document.at_xpath('//env:Envelope')
|
61
|
+
envelope.prepend_child('<env:Header><wsse:Security xmlns:wsse=\'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\' wsse:mustUnderstand=\'1\'/></env:Header>')
|
62
|
+
xml = document.to_s
|
63
|
+
|
64
|
+
signer = Signer.new(xml)
|
65
|
+
|
66
|
+
signer.cert = @public_cert
|
67
|
+
signer.private_key = @private_key
|
68
|
+
|
69
|
+
signer.document.xpath('//soapenv:Body', { soapenv: 'http://schemas.xmlsoap.org/soap/envelope/' }).each do |node|
|
70
|
+
signer.digest!(node)
|
71
|
+
end
|
72
|
+
|
73
|
+
signer.sign!(issuer_serial: true)
|
74
|
+
signed_xml = signer.to_xml
|
75
|
+
|
76
|
+
document = Nokogiri::XML(signed_xml)
|
77
|
+
x509data = document.at_xpath('//*[local-name()=\'X509Data\']')
|
78
|
+
new_data = x509data.clone()
|
79
|
+
new_data.set_attribute('xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#')
|
80
|
+
|
81
|
+
n = Nokogiri::XML::Node.new('wsse:SecurityTokenReference', document)
|
82
|
+
n.add_child(new_data)
|
83
|
+
x509data.add_next_sibling(n)
|
84
|
+
|
85
|
+
document
|
86
|
+
end
|
87
|
+
|
88
|
+
def wsdl_path
|
89
|
+
case @environment
|
90
|
+
when :production
|
91
|
+
'https://webpay3g.transbank.cl/WSWebpayTransaction/cxf/WSWebpayService?wsdl'
|
92
|
+
when :certification
|
93
|
+
'https://webpay3gint.transbank.cl/WSWebpayTransaction/cxf/WSWebpayService?wsdl'
|
94
|
+
when :integration
|
95
|
+
'https://webpay3gint.transbank.cl/WSWebpayTransaction/cxf/WSWebpayService?wsdl'
|
96
|
+
else
|
97
|
+
raise WebpayRails::InvalidEnvironment
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module WebpayRails
|
2
|
+
class TransactionResult
|
3
|
+
attr_reader :accounting_date, :buy_order, :card_number, :amount, :commerce_code,
|
4
|
+
:authorization_code, :payment_type_code, :response_code, :transaction_date,
|
5
|
+
:url_redirection, :vci
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@accounting_date = args[:accounting_date]
|
9
|
+
@buy_order = args[:buy_order]
|
10
|
+
@card_number = args[:card_number]
|
11
|
+
@amount = args[:amount]
|
12
|
+
@commerce_code = args[:commerce_code]
|
13
|
+
@authorization_code = args[:authorization_code]
|
14
|
+
@payment_type_code = args[:payment_type_code]
|
15
|
+
@response_code = args[:response_code]
|
16
|
+
@transaction_date = args[:transaction_date]
|
17
|
+
@url_redirection = args[:url_redirection]
|
18
|
+
@vci = args[:vci]
|
19
|
+
end
|
20
|
+
|
21
|
+
def approved?
|
22
|
+
@response_code == 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module WebpayRails
|
2
|
+
class Verifier
|
3
|
+
def self.verify(document, cert)
|
4
|
+
document = Nokogiri::XML(document.to_s, &:noblanks)
|
5
|
+
signed_info_node = document.at_xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo', {
|
6
|
+
ds: 'http://www.w3.org/2000/09/xmldsig#',
|
7
|
+
wsse: 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
|
8
|
+
soap: 'http://schemas.xmlsoap.org/soap/envelope/'
|
9
|
+
})
|
10
|
+
|
11
|
+
# check that digest match
|
12
|
+
check_digest(document, signed_info_node) && check_signature(document, signed_info_node, cert)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.check_digest(doc, signed_info_node)
|
16
|
+
signed_info_node.xpath("//ds:Reference", ds: 'http://www.w3.org/2000/09/xmldsig#').each do |node|
|
17
|
+
return false if !process_ref_node(doc, node)
|
18
|
+
end
|
19
|
+
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.check_signature(doc, signed_info_node, cert)
|
24
|
+
signed_info_canon = canonicalize(signed_info_node, ['soap'])
|
25
|
+
signature = doc.at_xpath('//wsse:Security/ds:Signature/ds:SignatureValue', {
|
26
|
+
ds: 'http://www.w3.org/2000/09/xmldsig#',
|
27
|
+
wsse: 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
|
28
|
+
}).text
|
29
|
+
|
30
|
+
cert.public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), signed_info_canon)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.digest(message)
|
34
|
+
OpenSSL::Digest::SHA1.new.reset.digest(message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.process_ref_node(doc, node)
|
38
|
+
uri = node.attr('URI')
|
39
|
+
element = doc.at_xpath("//*[@wsu:Id='#{uri[1..-1]}']", wsu: 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd')
|
40
|
+
target = canonicalize(element, nil)
|
41
|
+
my_digest_value = Base64.encode64(digest(target)).strip
|
42
|
+
digest_value = node.at_xpath('//ds:DigestValue', ds: 'http://www.w3.org/2000/09/xmldsig#').text
|
43
|
+
|
44
|
+
my_digest_value == digest_value
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.canonicalize(node = document, inclusive_namespaces=nil)
|
48
|
+
# The last argument should be exactly +nil+ to remove comments from result
|
49
|
+
node.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0, inclusive_namespaces, nil)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/webpay_rails.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
require 'signer'
|
4
|
+
require 'savon'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'base64'
|
7
|
+
require 'digest/sha1'
|
8
|
+
require 'openssl'
|
9
|
+
|
10
|
+
require 'webpay_rails/version'
|
11
|
+
require 'webpay_rails/errors'
|
12
|
+
require 'webpay_rails/soap'
|
13
|
+
require 'webpay_rails/verifier'
|
14
|
+
require 'webpay_rails/transaction'
|
15
|
+
require 'webpay_rails/transaction_result'
|
16
|
+
|
17
|
+
module WebpayRails
|
18
|
+
autoload :Base, 'webpay_rails/base'
|
19
|
+
|
20
|
+
def self.extended(base)
|
21
|
+
base.include WebpayRails::Base
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
silence_warnings do
|
5
|
+
ActiveRecord::Migration.verbose = false
|
6
|
+
ActiveRecord::Base.logger = Logger.new(nil)
|
7
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
8
|
+
end
|
9
|
+
|
10
|
+
ActiveRecord::Base.connection.instance_eval do
|
11
|
+
create_table :orders
|
12
|
+
end
|
13
|
+
|
14
|
+
class Order < ActiveRecord::Base
|
15
|
+
extend WebpayRails
|
16
|
+
|
17
|
+
webpay_rails({
|
18
|
+
commerce_code: 597020000541,
|
19
|
+
private_key: '-----BEGIN RSA PRIVATE KEY-----
|
20
|
+
MIIEpQIBAAKCAQEA0ClVcH8RC1u+KpCPUnzYSIcmyXI87REsBkQzaA1QJe4w/B7g
|
21
|
+
6KvKV9DaqfnNhMvd9/ypmGf0RDQPhlBbGlzymKz1xh0lQBD+9MZrg8Ju8/d1k0pI
|
22
|
+
b1QLQDnhRgR2T14ngXpP4PIQKtq7DsdHBybFU5vvAKVqdHvImZFzqexbZjXWxxhT
|
23
|
+
+/sGcD4Vs673fc6B+Xj2UrKF7QyV5pMDq0HCCLTMmafWAmNrHyl6imQM+bqC12gn
|
24
|
+
EEAEkrJiSO6P/21m9iDJs5KQanpJby0aGW8mocYRHDMHZjtTiIP0+JAJgL9KsH+r
|
25
|
+
Xdk2bT7aere7TzOK/bEwhkYEXnMMt/65vV6AfwIDAQABAoIBAHnIlOn6DTi99eXl
|
26
|
+
KVSzIb5dA747jZWMxFruL70ifM+UKSh30FGPoBP8ZtGnCiw1ManSMk6uEuSMKMEF
|
27
|
+
5iboVi4okqnTh2WSC/ec1m4BpPQqxKjlfrdTTjnHIxrZpXYNucMwkeci93569ZFR
|
28
|
+
2SY/8pZV1mBkZoG7ocLmq+qwE1EaBEL/sXMvuF/h08nJ71I4zcclpB8kN0yFrBCW
|
29
|
+
7scqOwTLiob2mmU2bFHOyyjTkGOlEsBQxhtVwVEt/0AFH/ucmMTP0vrKOA0HkhxM
|
30
|
+
oeR4k2z0qwTzZKXuEZtsau8a/9B3S3YcgoSOhRP/VdY1WL5hWDHeK8q1Nfq2eETX
|
31
|
+
jnQ4zjECgYEA7z2/biWe9nDyYDZM7SfHy1xF5Q3ocmv14NhTbt8iDlz2LsZ2JcPn
|
32
|
+
EMV++m88F3PYdFUOp4Zuw+eLJSrBqfuPYrTVNH0v/HdTqTS70R2YZCFb9g0ryaHV
|
33
|
+
TRwYovu/oQMV4LBSzrwdtCrcfUZDtqMYmmZfEkdjCWCEpEi36nlG0JMCgYEA3r49
|
34
|
+
o+soFIpDqLMei1tF+Ah/rm8oY5f4Wc82kmSgoPFCWnQEIW36i/GRaoQYsBp4loue
|
35
|
+
vyPuW+BzoZpVcJDuBmHY3UOLKr4ZldOn2KIj6sCQZ1mNKo5WuZ4YFeL5uyp9Hvio
|
36
|
+
TCPGeXghG0uIk4emSwolJVSbKSRi6SPsiANff+UCgYEAvNMRmlAbLQtsYb+565xw
|
37
|
+
NvO3PthBVL4dLL/Q6js21/tLWxPNAHWklDosxGCzHxeSCg9wJ40VM4425rjebdld
|
38
|
+
DF0Jwgnkq/FKmMxESQKA2tbxjDxNCTGv9tJsJ4dnch/LTrIcSYt0LlV9/WpN24LS
|
39
|
+
0lpmQzkQ07/YMQosDuZ1m/0CgYEAu9oHlEHTmJcO/qypmu/ML6XDQPKARpY5Hkzy
|
40
|
+
gj4ZdgJianSjsynUfsepUwK663I3twdjR2JfON8vxd+qJPgltf45bknziYWvgDtz
|
41
|
+
t/Duh6IFZxQQSQ6oN30MZRD6eo4X3dHp5eTaE0Fr8mAefAWQCoMw1q3m+ai1PlhM
|
42
|
+
uFzX4r0CgYEArx4TAq+Z4crVCdABBzAZ7GvvAXdxvBo0AhD9IddSWVTCza972wta
|
43
|
+
5J2rrS/ye9Tfu5j2IbTHaLDz14mwMXr1S4L39UX/NifLc93KHie/yjycCuu4uqNo
|
44
|
+
MtdweTnQt73lN2cnYedRUhw9UTfPzYu7jdXCUAyAD4IEjFQrswk2x04=
|
45
|
+
-----END RSA PRIVATE KEY-----',
|
46
|
+
public_cert: '-----BEGIN CERTIFICATE-----
|
47
|
+
MIIDujCCAqICCQCZ42cY33KRTzANBgkqhkiG9w0BAQsFADCBnjELMAkGA1UEBhMC
|
48
|
+
Q0wxETAPBgNVBAgMCFNhbnRpYWdvMRIwEAYDVQQKDAlUcmFuc2JhbmsxETAPBgNV
|
49
|
+
BAcMCFNhbnRpYWdvMRUwEwYDVQQDDAw1OTcwMjAwMDA1NDExFzAVBgNVBAsMDkNh
|
50
|
+
bmFsZXNSZW1vdG9zMSUwIwYJKoZIhvcNAQkBFhZpbnRlZ3JhZG9yZXNAdmFyaW9z
|
51
|
+
LmNsMB4XDTE2MDYyMjIxMDkyN1oXDTI0MDYyMDIxMDkyN1owgZ4xCzAJBgNVBAYT
|
52
|
+
AkNMMREwDwYDVQQIDAhTYW50aWFnbzESMBAGA1UECgwJVHJhbnNiYW5rMREwDwYD
|
53
|
+
VQQHDAhTYW50aWFnbzEVMBMGA1UEAwwMNTk3MDIwMDAwNTQxMRcwFQYDVQQLDA5D
|
54
|
+
YW5hbGVzUmVtb3RvczElMCMGCSqGSIb3DQEJARYWaW50ZWdyYWRvcmVzQHZhcmlv
|
55
|
+
cy5jbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANApVXB/EQtbviqQ
|
56
|
+
j1J82EiHJslyPO0RLAZEM2gNUCXuMPwe4OirylfQ2qn5zYTL3ff8qZhn9EQ0D4ZQ
|
57
|
+
Wxpc8pis9cYdJUAQ/vTGa4PCbvP3dZNKSG9UC0A54UYEdk9eJ4F6T+DyECrauw7H
|
58
|
+
RwcmxVOb7wClanR7yJmRc6nsW2Y11scYU/v7BnA+FbOu933Ogfl49lKyhe0MleaT
|
59
|
+
A6tBwgi0zJmn1gJjax8peopkDPm6gtdoJxBABJKyYkjuj/9tZvYgybOSkGp6SW8t
|
60
|
+
GhlvJqHGERwzB2Y7U4iD9PiQCYC/SrB/q13ZNm0+2nq3u08ziv2xMIZGBF5zDLf+
|
61
|
+
ub1egH8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdgNpIS2NZFx5PoYwJZf8faze
|
62
|
+
NmKQg73seDGuP8d8w/CZf1Py/gsJFNbh4CEySWZRCzlOKxzmtPTmyPdyhObjMA8E
|
63
|
+
Adps9DtgiN2ITSF1HUFmhMjI5V7U2L9LyEdpUaieYyPBfxiicdWz2YULVuOYDJHR
|
64
|
+
n05jlj/EjYa5bLKs/yggYiqMkZdIX8NiLL6ZTERIvBa6azDKs6yDsCsnE1M5tzQI
|
65
|
+
VVEkZtEfil6E1tz8v3yLZapLt+8jmPq1RCSx3Zh4fUkxBTpUW/9SWUNEXbKK7bB3
|
66
|
+
zfB3kGE55K5nxHKfQlrqdHLcIo+vdShATwYnmhUkGxUnM9qoCDlB8lYu3rFi9w==
|
67
|
+
-----END CERTIFICATE-----',
|
68
|
+
webpay_cert: '-----BEGIN CERTIFICATE-----
|
69
|
+
MIIDKTCCAhECBFZl7uIwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ0wxDjAMBgNVBAgMBUNo
|
70
|
+
aWxlMREwDwYDVQQHDAhTYW50aWFnbzEMMAoGA1UECgwDa2R1MQwwCgYDVQQLDANrZHUxCzAJBgNV
|
71
|
+
BAMMAjEwMB4XDTE1MTIwNzIwNDEwNloXDTE4MDkwMjIwNDEwNlowWTELMAkGA1UEBhMCQ0wxDjAM
|
72
|
+
BgNVBAgMBUNoaWxlMREwDwYDVQQHDAhTYW50aWFnbzEMMAoGA1UECgwDa2R1MQwwCgYDVQQLDANr
|
73
|
+
ZHUxCzAJBgNVBAMMAjEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAizJUWTDC7nfP
|
74
|
+
3jmZpWXFdG9oKyBrU0Bdl6fKif9a1GrwevThsU5Dq3wiRfYvomStNjFDYFXOs9pRIxqX2AWDybjA
|
75
|
+
X/+bdDTVbM+xXllA9stJY8s7hxAvwwO7IEuOmYDpmLKP7J+4KkNH7yxsKZyLL9trG3iSjV6Y6SO5
|
76
|
+
EEhUsdxoJFAow/h7qizJW0kOaWRcljf7kpqJAL3AadIuqV+hlf+Ts/64aMsfSJJA6xdbdp9ddgVF
|
77
|
+
oqUl1M8vpmd4glxlSrYmEkbYwdI9uF2d6bAeaneBPJFZr6KQqlbbrVyeJZqmMlEPy0qPco1TIxrd
|
78
|
+
EHlXgIFJLyyMRAyjX9i4l70xjwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBn3tUPS6e2USgMrPKp
|
79
|
+
sxU4OTfW64+mfD6QrVeBOh81f6aGHa67sMJn8FE/cG6jrUmX/FP1/Cpbpvkm5UUlFKpgaFfHv+Kg
|
80
|
+
CpEvgcRIv/OeIi6Jbuu3NrPdGPwzYkzlOQnmgio5RGb6GSs+OQ0mUWZ9J1+YtdZc+xTga0x7nsCT
|
81
|
+
5xNcUXsZKhyjoKhXtxJm3eyB3ysLNyuL/RHy/EyNEWiUhvt1SIePnW+Y4/cjQWYwNqSqMzTSW9TP
|
82
|
+
2QR2bX/W2H6ktRcLsgBK9mq7lE36p3q6c9DtZJE+xfA4NGCYWM9hd8pbusnoNO7AFxJZOuuvLZI7
|
83
|
+
JvD7YLhPvCYKry7N6x3l
|
84
|
+
-----END CERTIFICATE-----',
|
85
|
+
environment: :integration
|
86
|
+
})
|
87
|
+
end
|
88
|
+
|
89
|
+
describe WebpayRails do
|
90
|
+
let(:amount) { 1000 }
|
91
|
+
let(:buy_order) { rand(1111111..9999999) }
|
92
|
+
let(:session_id) { 'aj2h4kj2' }
|
93
|
+
let(:result_url) { 'http://localhost:3000/tbknormal?option=result' }
|
94
|
+
let(:final_url) { 'http://localhost:3000/tbknormal?option=final' }
|
95
|
+
|
96
|
+
describe WebpayRails::Transaction do
|
97
|
+
describe "when all is ok" do
|
98
|
+
let(:transaction) { Order.init_transaction(amount, buy_order, session_id, result_url, final_url) }
|
99
|
+
|
100
|
+
it { expect(transaction).to be_kind_of(WebpayRails::Transaction) }
|
101
|
+
|
102
|
+
describe ".token" do
|
103
|
+
it { expect(transaction.token).not_to be_blank }
|
104
|
+
end
|
105
|
+
|
106
|
+
describe ".url" do
|
107
|
+
it { expect(transaction.url).not_to be_blank }
|
108
|
+
end
|
109
|
+
|
110
|
+
describe ".success?" do
|
111
|
+
it { expect(transaction.success?).to be_truthy }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "when not" do
|
116
|
+
it { expect{Order.init_transaction(amount, buy_order, session_id, '', '')}.to raise_error(WebpayRails::FailedInitTransaction) }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe WebpayRails::TransactionResult do
|
121
|
+
pending "comming soon"
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require "webpay_rails/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.name = "webpay_rails"
|
9
|
+
s.version = WebpayRails::VERSION
|
10
|
+
s.summary = "WebpayRails is an easy solution for integrate Transbank Webpay in Rails applications."
|
11
|
+
s.description = s.summary
|
12
|
+
s.authors = ["Sebastián Orellana"]
|
13
|
+
s.email = ["limcross@gmail.com"]
|
14
|
+
s.homepage = "https://github.com/limcross/webpay_rails"
|
15
|
+
s.license = "MIT"
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
|
20
|
+
s.required_ruby_version = '>= 2.2.2'
|
21
|
+
|
22
|
+
s.add_dependency 'signer', '~> 1.4.3'
|
23
|
+
s.add_dependency 'savon', '~> 2'
|
24
|
+
s.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.7.2'
|
25
|
+
s.add_dependency 'activesupport', '>= 3.2'
|
26
|
+
|
27
|
+
s.add_development_dependency 'rspec', '~> 3.4'
|
28
|
+
s.add_development_dependency 'activerecord', '>= 3.2'
|
29
|
+
s.add_development_dependency 'sqlite3'
|
30
|
+
s.add_development_dependency 'rake'
|
31
|
+
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: webpay_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sebastián Orellana
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: signer
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.4.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: savon
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.6.7.2
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.6'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.6.7.2
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: activesupport
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.2'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.4'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.4'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: activerecord
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.2'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.2'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: sqlite3
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rake
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
description: WebpayRails is an easy solution for integrate Transbank Webpay in Rails
|
132
|
+
applications.
|
133
|
+
email:
|
134
|
+
- limcross@gmail.com
|
135
|
+
executables: []
|
136
|
+
extensions: []
|
137
|
+
extra_rdoc_files: []
|
138
|
+
files:
|
139
|
+
- ".gitignore"
|
140
|
+
- ".rspec"
|
141
|
+
- ".travis.yml"
|
142
|
+
- Gemfile
|
143
|
+
- MIT-LICENSE
|
144
|
+
- README.md
|
145
|
+
- Rakefile
|
146
|
+
- lib/webpay_rails.rb
|
147
|
+
- lib/webpay_rails/base.rb
|
148
|
+
- lib/webpay_rails/errors.rb
|
149
|
+
- lib/webpay_rails/soap.rb
|
150
|
+
- lib/webpay_rails/transaction.rb
|
151
|
+
- lib/webpay_rails/transaction_result.rb
|
152
|
+
- lib/webpay_rails/verifier.rb
|
153
|
+
- lib/webpay_rails/version.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- spec/webpay_rails_spec.rb
|
156
|
+
- webpay_rails.gemspec
|
157
|
+
homepage: https://github.com/limcross/webpay_rails
|
158
|
+
licenses:
|
159
|
+
- MIT
|
160
|
+
metadata: {}
|
161
|
+
post_install_message:
|
162
|
+
rdoc_options: []
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 2.2.2
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
requirements: []
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 2.5.1
|
178
|
+
signing_key:
|
179
|
+
specification_version: 4
|
180
|
+
summary: WebpayRails is an easy solution for integrate Transbank Webpay in Rails applications.
|
181
|
+
test_files:
|
182
|
+
- spec/spec_helper.rb
|
183
|
+
- spec/webpay_rails_spec.rb
|