payu 0.7.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 +15 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +171 -0
- data/Rakefile +1 -0
- data/init.rb +2 -0
- data/lib/payu.rb +87 -0
- data/lib/payu/errors.rb +47 -0
- data/lib/payu/gateway.rb +76 -0
- data/lib/payu/helpers.rb +33 -0
- data/lib/payu/pos.rb +80 -0
- data/lib/payu/response.rb +18 -0
- data/lib/payu/signature.rb +18 -0
- data/lib/payu/transaction.rb +94 -0
- data/lib/payu/version.rb +5 -0
- data/payu.gemspec +21 -0
- data/spec/response_spec.rb +73 -0
- data/spec/signature_spec.rb +41 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/transaction_spec.rb +136 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTM2OGIzMTZhY2FlZjM5OGZiZjNlODAwYjYyNjYyZWU3Y2JmOTYzMQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MWQzMGE4YjdhY2E3YjY2NDNkOTkwMzdmNDZhOGU3MDJjZDNlMmU0Zg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YTUzYzBmNGQ4NmE2YTg5NTFlNzU1OWYwMjMxN2VkNmQxNjNmZjJjNjQ3YjI2
|
10
|
+
NmYyMzBmMjRlMjQ4MTY5NmJjYjA3ZmExNmMwMmE5YWFiZDBiNDBiMzZmODM5
|
11
|
+
MmRmZDM3NmJmOGNlMmRlN2FiZTFjMzEyMGEwOTVjNTA2MTkzYWE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGY4ZGVkNWY1MWEwN2EzYzcwNmZjNGM3N2UwN2MwN2MxOWEwODhiZDg2YmNi
|
14
|
+
OWFkMzcwZWMyNjA3ZmUxYzRhMTEyYWE0NzI4MmU3YjNkYjRkOGVkMGQ0MjE4
|
15
|
+
NTJjMTc1YzgzNzU1YzgyZmRiYjIzMDIyN2VhMDUyMGJiOTFmMDI=
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Michał Młoźniak
|
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,171 @@
|
|
1
|
+
# payu
|
2
|
+
|
3
|
+
Simple library for accepting payments via PayU.
|
4
|
+
|
5
|
+
By [Visuality](http://www.visuality.pl).
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
* No dependencies on ActiveSupport or any similar libraries, so it should work in any Ruby application or framework
|
10
|
+
* Simple configuration (multiple points of sale)
|
11
|
+
* Automatic signature generation and verification
|
12
|
+
* View helpers for generating payment form and verifying signatures
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
In your Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'payu'
|
20
|
+
````
|
21
|
+
|
22
|
+
Or, from the command line:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem install payu
|
26
|
+
```
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
This gem implements only core functionality for integrating with PayU gateway. It is designed to work with any ruby framework or plain ruby application. To integrate it in your app you need to do some work, but it is really simple.
|
31
|
+
|
32
|
+
### Create Pos
|
33
|
+
|
34
|
+
Your app will interact with PayU via point of sale (Pos). You can create it on PayU website and get its credentials. With these credentials you can create Pos instance:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
pos = Payu::Pos.new :pos_id => '12345', :pos_auth_key => 'abcdefghijk', :key1 => 'xxxxxxxx', :key2 => 'xxxxxxxx'
|
38
|
+
```
|
39
|
+
|
40
|
+
You can also load Pos configuration from yaml file. For example config/payu.yml:
|
41
|
+
|
42
|
+
```yaml
|
43
|
+
bank:
|
44
|
+
pos_id: 12345
|
45
|
+
pos_auth_key: XXX
|
46
|
+
key1: XXX
|
47
|
+
key2: XXX
|
48
|
+
type: default
|
49
|
+
|
50
|
+
sms:
|
51
|
+
pos_id: 56789
|
52
|
+
pos_auth_key: XXX
|
53
|
+
key1: XXX
|
54
|
+
key2: XXX
|
55
|
+
type: sms_premium
|
56
|
+
```
|
57
|
+
|
58
|
+
Then add new initializer config/initializers/payu.rb:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
Payu.load_pos_from_yaml(Rails.root.join('config', 'payu.yml'))
|
62
|
+
```
|
63
|
+
|
64
|
+
Now you can access them by name or pos_id:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Payu['bank']
|
68
|
+
Payu[:bank]
|
69
|
+
Payu[12345]
|
70
|
+
Payu['56789']
|
71
|
+
```
|
72
|
+
|
73
|
+
### Create new payment
|
74
|
+
|
75
|
+
To create new payment:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
@transaction = pos.new_transaction(:first_name => 'John', :last_name => 'Doe', :email => 'john.doe@example.org', :client_ip => '1.2.3.4', :amount => 10000)
|
79
|
+
```
|
80
|
+
|
81
|
+
Now you need to build form with this transaction object:
|
82
|
+
|
83
|
+
```
|
84
|
+
<%= form_tag(@transaction.new_url) do %>
|
85
|
+
<%= payu_hidden_fields(@transaction) %>
|
86
|
+
<%= submit_tag 'Pay' %>
|
87
|
+
<% end %>
|
88
|
+
```
|
89
|
+
|
90
|
+
### Read payment status
|
91
|
+
|
92
|
+
You can check status of any payment:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
response = pos.get(123456789)
|
96
|
+
|
97
|
+
if response.status == 'OK'
|
98
|
+
if response.completed?
|
99
|
+
# payment completed
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
123456789 is a payment session id. It is automatically generated when you create new transaction.
|
105
|
+
|
106
|
+
### Confirm payment
|
107
|
+
|
108
|
+
By default when somebody sends a payment it is automatically accepted. You can turn it off and confirm every payment:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
response = pos.confirm(123456789)
|
112
|
+
```
|
113
|
+
|
114
|
+
### Cancel payment
|
115
|
+
|
116
|
+
You can cancel payment:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
response = pos.cancel(123456789)
|
120
|
+
```
|
121
|
+
|
122
|
+
### Handle Payu callbacks
|
123
|
+
|
124
|
+
When payment status changes, Payu will send report to your application. You need controller to handle these reports:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
|
128
|
+
class PayuController < ApplicationController
|
129
|
+
include Payu::Helpers
|
130
|
+
skip_before_filter :verify_authenticity_token
|
131
|
+
|
132
|
+
def ok
|
133
|
+
# successful redirect
|
134
|
+
end
|
135
|
+
|
136
|
+
def error
|
137
|
+
# failed redirect
|
138
|
+
end
|
139
|
+
|
140
|
+
def report
|
141
|
+
payu_verify_params(params)
|
142
|
+
|
143
|
+
response = Payu['main'].get params[:session_id]
|
144
|
+
|
145
|
+
if response.status == 'OK'
|
146
|
+
order = Order.find(response.trans_order_id)
|
147
|
+
|
148
|
+
if response.completed? && order.present?
|
149
|
+
# mark order as paid
|
150
|
+
else
|
151
|
+
# payment not completed
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
render :text => 'OK'
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
And routes:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
match '/payu/ok' => 'payu#ok'
|
163
|
+
match '/payu/error' => 'payu#error'
|
164
|
+
match '/payu/report' => 'payu#report'
|
165
|
+
```
|
166
|
+
|
167
|
+
Actions ok and error are pages where user is redirected after payment. You need to enter these url on Payu website.
|
168
|
+
|
169
|
+
## Copyright
|
170
|
+
|
171
|
+
Copyright (c) 2013 Michał Młoźniak. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/init.rb
ADDED
data/lib/payu.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'payu/version'
|
6
|
+
require 'payu/pos'
|
7
|
+
require 'payu/transaction'
|
8
|
+
require 'payu/response'
|
9
|
+
require 'payu/signature'
|
10
|
+
require 'payu/errors'
|
11
|
+
|
12
|
+
module Payu
|
13
|
+
ENCODINGS = ['ISO', 'WIN', 'UTF']
|
14
|
+
|
15
|
+
@@pos_table = {}
|
16
|
+
|
17
|
+
class SignatureInvalid < StandardError; end
|
18
|
+
class PosNotFound < StandardError; end
|
19
|
+
class RequestFailed < StandardError; end
|
20
|
+
class PosInvalid < StandardError; end
|
21
|
+
class TransactionInvalid < StandardError; end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
|
25
|
+
# Loads payments.yml file and creates specified Pos objects
|
26
|
+
def load_pos_from_yaml(filename)
|
27
|
+
if File.exist?(filename)
|
28
|
+
config = YAML.load_file(filename)
|
29
|
+
config.each do |name, config|
|
30
|
+
pos = Pos.new(
|
31
|
+
:pos_id => config['pos_id'],
|
32
|
+
:pos_auth_key => config['pos_auth_key'],
|
33
|
+
:key1 => config['key1'],
|
34
|
+
:key2 => config['key2'],
|
35
|
+
:variant => config['variant'],
|
36
|
+
:add_signature => config['add_signature'],
|
37
|
+
:test_payment => config['test_payment']
|
38
|
+
)
|
39
|
+
@@pos_table[name] = pos
|
40
|
+
end
|
41
|
+
|
42
|
+
true
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_pos(name, pos)
|
49
|
+
@@pos_table[name.to_s] = pos
|
50
|
+
end
|
51
|
+
|
52
|
+
# Combined accessor, returns Pos object with given pos_id or name
|
53
|
+
#
|
54
|
+
# @param [String, Integer] name_or_id name or pos_id of Pos
|
55
|
+
# @return [Object] the Pos object
|
56
|
+
def [](name_or_id)
|
57
|
+
get_pos_by_name(name_or_id) || get_pos_by_id(name_or_id) || raise(PosNotFound)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns Pos object with given name, the same as in payments.yml file
|
61
|
+
#
|
62
|
+
# @param [String] name name of Pos
|
63
|
+
# @return [Object] the Pos object
|
64
|
+
def get_pos_by_name(name)
|
65
|
+
@@pos_table[name.to_s]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns Pos object with given pos_id, the same as in payments.yml file
|
69
|
+
#
|
70
|
+
# @param [Integer] id pos_id of Pos
|
71
|
+
# @return [Object] the Pos object
|
72
|
+
def get_pos_by_id(pos_id)
|
73
|
+
pos_id = pos_id.to_i rescue 0
|
74
|
+
@@pos_table.each do |k, v|
|
75
|
+
return v if v.pos_id == pos_id
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if defined?(Rails)
|
83
|
+
require "payu/helpers"
|
84
|
+
|
85
|
+
ActionView::Base.send(:include, Payu::Helpers)
|
86
|
+
ActionController::Base.send(:include, Payu::Helpers)
|
87
|
+
end
|
data/lib/payu/errors.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Payu
|
4
|
+
ERRORS = {
|
5
|
+
100 => 'Missing parameter pos_id',
|
6
|
+
101 => 'Missing parameter session_id',
|
7
|
+
102 => 'Missing parameter ts',
|
8
|
+
103 => 'Missing parameter sig',
|
9
|
+
104 => 'Missing parameter desc',
|
10
|
+
105 => 'Missing parameter client_ip',
|
11
|
+
106 => 'Missing parameter first_name',
|
12
|
+
107 => 'Missing parameter last_name',
|
13
|
+
108 => 'Missing parameter street',
|
14
|
+
109 => 'Missing parameter city',
|
15
|
+
110 => 'Missing parameter post_code',
|
16
|
+
111 => 'Missing parameter amount',
|
17
|
+
112 => 'Invalid bank account number',
|
18
|
+
113 => 'Missing parameter email',
|
19
|
+
114 => 'Missing parameter phone',
|
20
|
+
200 => 'Temporary error',
|
21
|
+
201 => 'Temporary database error',
|
22
|
+
202 => 'POS blocked',
|
23
|
+
203 => 'Invalid pay_type value for provided pos_id',
|
24
|
+
204 => 'Selected payment method (pay_type value) is temporary unavailable for provided pos_id',
|
25
|
+
205 => 'Provided amount is smaller than minimal amount',
|
26
|
+
206 => 'Provided amount is larger than maximal amount',
|
27
|
+
207 => 'Transaction limit exceeded',
|
28
|
+
208 => 'ExpressPayment has not been activated',
|
29
|
+
209 => 'Invalid pos_id or pos_auth_key',
|
30
|
+
500 => 'Transaction does not exist',
|
31
|
+
501 => 'Unauthorized for this transaction',
|
32
|
+
502 => 'Transaction already started',
|
33
|
+
503 => 'Transaction already authorized',
|
34
|
+
504 => 'Transaction already canceled',
|
35
|
+
505 => 'Confirm order already sent',
|
36
|
+
506 => 'Transaction already confirmed',
|
37
|
+
507 => 'Error while returning funds to client',
|
38
|
+
599 => 'Invalid transaction status. Please contact PayU support',
|
39
|
+
999 => 'Critical error'
|
40
|
+
}
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def get_error_description(code)
|
44
|
+
return ERRORS[code.to_i]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/payu/gateway.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module Payu
|
7
|
+
class Gateway
|
8
|
+
attr_reader :encoding, :key1, :key2, :pos_id
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@encoding = options[:encoding]
|
12
|
+
@key1 = options[:key1]
|
13
|
+
@key2 = options[:key2]
|
14
|
+
@pos_id = options[:pos_id]
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(session_id)
|
18
|
+
send_request("/paygw/#{encoding}/Payment/get/txt", session_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def confirm(session_id)
|
22
|
+
send_request("/paygw/#{encoding}/Payment/confirm/txt", session_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def cancel(session_id)
|
26
|
+
send_request("/paygw/#{encoding}/Payment/cancel/txt", session_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def send_request(url, session_id)
|
31
|
+
data = prepare_data(session_id)
|
32
|
+
connection = Net::HTTP.new('www.platnosci.pl', 443)
|
33
|
+
connection.use_ssl = true
|
34
|
+
|
35
|
+
http_response = connection.start do |http|
|
36
|
+
post = Net::HTTP::Post.new(url)
|
37
|
+
post.set_form_data(data)
|
38
|
+
http.request(post)
|
39
|
+
end
|
40
|
+
|
41
|
+
if http_response.code == '200'
|
42
|
+
response = Response.parse(http_response.body)
|
43
|
+
verify!(response) if response.status == 'OK'
|
44
|
+
|
45
|
+
return response
|
46
|
+
else
|
47
|
+
raise RequestFailed
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def prepare_data(session_id)
|
52
|
+
ts = (Time.now.to_f * 1000).to_i
|
53
|
+
sig = Signature.generate(pos_id, session_id, ts, key1)
|
54
|
+
|
55
|
+
{
|
56
|
+
'pos_id' => pos_id,
|
57
|
+
'session_id' => session_id,
|
58
|
+
'ts' => ts,
|
59
|
+
'sig' => sig
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def verify!(response)
|
64
|
+
Signature.verify!(response.trans_sig,
|
65
|
+
response.trans_pos_id,
|
66
|
+
response.trans_session_id,
|
67
|
+
response.trans_order_id,
|
68
|
+
response.trans_status,
|
69
|
+
response.trans_amount,
|
70
|
+
response.trans_desc,
|
71
|
+
response.trans_ts,
|
72
|
+
key2
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/payu/helpers.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Payu
|
4
|
+
module Helpers
|
5
|
+
def payu_hidden_fields(transaction)
|
6
|
+
html = ""
|
7
|
+
|
8
|
+
%w(pos_id pos_auth_key pay_type session_id amount amount_netto desc
|
9
|
+
order_id desc2 trsDesc first_name last_name street street_hn
|
10
|
+
street_an city post_code country email phone language client_ip
|
11
|
+
js payback_login sig ts
|
12
|
+
).each do |field|
|
13
|
+
value = transaction.send(field)
|
14
|
+
html << hidden_field_tag(field, value) unless value.blank?
|
15
|
+
end
|
16
|
+
|
17
|
+
html.html_safe
|
18
|
+
end
|
19
|
+
|
20
|
+
def payu_verify_params(params)
|
21
|
+
pos_id = params['pos_id']
|
22
|
+
pos = Payu[pos_id]
|
23
|
+
|
24
|
+
Signature.verify!(
|
25
|
+
params['sig'],
|
26
|
+
params['pos_id'],
|
27
|
+
params['session_id'],
|
28
|
+
params['ts'],
|
29
|
+
pos.key2
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/payu/pos.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'payu/gateway'
|
5
|
+
|
6
|
+
module Payu
|
7
|
+
class Pos
|
8
|
+
TYPES = ['default', 'sms']
|
9
|
+
|
10
|
+
attr_reader :pos_id, :pos_auth_key, :key1, :key2, :type, :encoding, :variant
|
11
|
+
|
12
|
+
# Creates new Pos instance
|
13
|
+
# @param [Hash] options options hash
|
14
|
+
# @return [Object] Pos object
|
15
|
+
def initialize(options)
|
16
|
+
@pos_id = options[:pos_id].to_i
|
17
|
+
@pos_auth_key = options[:pos_auth_key]
|
18
|
+
@key1 = options[:key1]
|
19
|
+
@key2 = options[:key2]
|
20
|
+
@variant = options[:variant] || 'default'
|
21
|
+
@encoding = options[:encoding] || 'UTF'
|
22
|
+
@test_payment = options[:test_payment] || false
|
23
|
+
@add_signature = options[:add_signature] || false
|
24
|
+
|
25
|
+
raise PosInvalid.new('Missing pos_id parameter') if pos_id.nil? || pos_id == 0
|
26
|
+
raise PosInvalid.new('Missing pos_auth_key parameter') if pos_auth_key.nil? || pos_auth_key == ''
|
27
|
+
raise PosInvalid.new('Missing key1 parameter') if key1.nil? || key1 == ''
|
28
|
+
raise PosInvalid.new('Missing key2 parameter') if key2.nil? || key2 == ''
|
29
|
+
raise PosInvalid.new("Invalid variant parameter, expected one of these: #{TYPES.join(', ')}") unless TYPES.include?(variant)
|
30
|
+
raise PosInvalid.new("Invalid encoding parameter, expected one of these: #{ENCODINGS.join(', ')}") unless ENCODINGS.include?(encoding)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates new transaction
|
34
|
+
# @param [Hash] options options hash for new transaction
|
35
|
+
# @return [Object] Transaction object
|
36
|
+
def new_transaction(options = {})
|
37
|
+
options = options.dup
|
38
|
+
|
39
|
+
options.merge!({
|
40
|
+
:pos_id => @pos_id,
|
41
|
+
:pos_auth_key => @pos_auth_key,
|
42
|
+
:key1 => @key1,
|
43
|
+
:add_signature => add_signature?,
|
44
|
+
:encoding => encoding,
|
45
|
+
:variant => variant
|
46
|
+
})
|
47
|
+
|
48
|
+
if !options.has_key?(:pay_type)
|
49
|
+
options[:pay_type] = test_payment? ? 't' : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
Transaction.new(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def get(session_id)
|
56
|
+
get_gateway.get(session_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def confirm(session_id)
|
60
|
+
get_gateway.confirm(session_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def cancel(session_id)
|
64
|
+
get_gateway.cancel(session_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_signature?
|
68
|
+
@add_signature
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def get_gateway
|
73
|
+
Gateway.new(:encoding => encoding, :key1 => key1, :key2 => key2, :pos_id => pos_id)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_payment?
|
77
|
+
@test_payment && @variant == 'default'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Payu
|
4
|
+
class Response < OpenStruct
|
5
|
+
PATTERN = /^(\w+):(?:[ ])?(.*)$/
|
6
|
+
|
7
|
+
def self.parse(body)
|
8
|
+
temp = body.gsub("\r", "")
|
9
|
+
data = temp.scan(PATTERN)
|
10
|
+
|
11
|
+
new(data)
|
12
|
+
end
|
13
|
+
|
14
|
+
def completed?
|
15
|
+
trans_status.to_i == 99
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Payu
|
6
|
+
class SignatureInvalid < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class Signature
|
10
|
+
def self.generate(*params)
|
11
|
+
Digest::MD5.hexdigest(params.join)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.verify!(expected, *params)
|
15
|
+
raise SignatureInvalid if expected != generate(params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Payu
|
4
|
+
class Transaction
|
5
|
+
attr_accessor :pos_id, :pos_auth_key, :pay_type, :session_id, :amount, :amount_netto, :desc,
|
6
|
+
:order_id, :desc2, :trsDesc, :first_name, :last_name, :street, :street_hn,
|
7
|
+
:street_an, :city, :post_code, :country, :email, :phone, :language, :client_ip,
|
8
|
+
:js, :payback_login, :sig, :ts, :key1, :add_signature, :variant, :encoding
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
options[:session_id] ||= generate_timestamp
|
12
|
+
|
13
|
+
options.each do |name, value|
|
14
|
+
send("#{name.to_s}=", value)
|
15
|
+
end
|
16
|
+
|
17
|
+
validate!
|
18
|
+
|
19
|
+
if options[:add_signature]
|
20
|
+
self.ts = generate_timestamp
|
21
|
+
self.sig = generate_signature
|
22
|
+
end
|
23
|
+
|
24
|
+
if variant == 'sms'
|
25
|
+
self.amount_netto = amount
|
26
|
+
self.amount = nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_url
|
31
|
+
if variant == 'sms'
|
32
|
+
return "https://www.platnosci.pl/paygw/#{encoding}/NewSMS"
|
33
|
+
else
|
34
|
+
return "https://www.platnosci.pl/paygw/#{encoding}/NewPayment"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def generate_timestamp
|
40
|
+
(Time.now.to_f * 100).to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_signature
|
44
|
+
Signature.generate(
|
45
|
+
pos_id,
|
46
|
+
pay_type,
|
47
|
+
session_id,
|
48
|
+
pos_auth_key,
|
49
|
+
amount,
|
50
|
+
desc,
|
51
|
+
desc2,
|
52
|
+
trsDesc,
|
53
|
+
order_id,
|
54
|
+
first_name,
|
55
|
+
last_name,
|
56
|
+
payback_login,
|
57
|
+
street,
|
58
|
+
street_hn,
|
59
|
+
street_an,
|
60
|
+
city,
|
61
|
+
post_code,
|
62
|
+
country,
|
63
|
+
email,
|
64
|
+
phone,
|
65
|
+
language,
|
66
|
+
client_ip,
|
67
|
+
ts,
|
68
|
+
key1
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def validate!
|
74
|
+
invalid_attributes = []
|
75
|
+
|
76
|
+
[:pos_id, :pos_auth_key, :session_id, :desc, :first_name, :last_name, :email, :client_ip, :amount].each do |name|
|
77
|
+
invalid_attributes << name if attribute_empty?(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
if attribute_empty?(:amount)
|
81
|
+
invalid_attributes << :amount
|
82
|
+
end
|
83
|
+
|
84
|
+
if invalid_attributes.any?
|
85
|
+
raise TransactionInvalid.new("Attributes required: #{invalid_attributes.join(', ')}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def attribute_empty?(name)
|
90
|
+
value = send(name)
|
91
|
+
value.nil? || value.respond_to?(:empty?) && value.empty?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/payu/version.rb
ADDED
data/payu.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require "payu/version"
|
5
|
+
require "date"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "payu"
|
9
|
+
s.version = Payu::VERSION
|
10
|
+
s.date = Date.today
|
11
|
+
s.summary = "Simple integration with PayU gateway"
|
12
|
+
s.description = "Simple integration with PayU gateway"
|
13
|
+
s.author = "Michał Młoźniak"
|
14
|
+
s.email = "michal.mlozniak@visuality.pl"
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.homepage = "http://github.com/ronin/payu"
|
17
|
+
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe "Payu::Response" do
|
6
|
+
@key1 = '3d91f185cacad7c1d830d1472dfaacc5'
|
7
|
+
@key2 = 'a747e4b3e49e17459a8a402518d36022'
|
8
|
+
|
9
|
+
it "should parse payment/get response" do
|
10
|
+
body = <<-EOF
|
11
|
+
status:OK
|
12
|
+
trans_id:7
|
13
|
+
trans_pos_id:1
|
14
|
+
trans_session_id:417419
|
15
|
+
trans_order_id:
|
16
|
+
trans_amount:200
|
17
|
+
trans_status:5
|
18
|
+
trans_pay_type:t
|
19
|
+
trans_pay_gw_name:pt
|
20
|
+
trans_desc:Wpłata dla test@test.pl
|
21
|
+
trans_desc2:
|
22
|
+
trans_create:2004-08-2310:39:52
|
23
|
+
trans_init:2004-08-3113:42:43
|
24
|
+
trans_sent:2004-08-3113:48:13
|
25
|
+
trans_recv:
|
26
|
+
trans_cancel:
|
27
|
+
trans_auth_fraud:0
|
28
|
+
trans_ts:1094205761232
|
29
|
+
trans_sig:b6d68525f724a6d69fb1260874924759
|
30
|
+
EOF
|
31
|
+
|
32
|
+
response = Payu::Response.parse(body)
|
33
|
+
|
34
|
+
response.status.should == 'OK'
|
35
|
+
response.trans_id.should == '7'
|
36
|
+
response.trans_pos_id.should == '1'
|
37
|
+
response.trans_session_id.should == '417419'
|
38
|
+
response.trans_order_id.should == ''
|
39
|
+
response.trans_amount.should == '200'
|
40
|
+
response.trans_status.should == '5'
|
41
|
+
response.trans_pay_type.should == 't'
|
42
|
+
response.trans_pay_gw_name.should == 'pt'
|
43
|
+
response.trans_desc.should == 'Wpłata dla test@test.pl'
|
44
|
+
response.trans_desc2.should == ''
|
45
|
+
response.trans_create.should == '2004-08-2310:39:52'
|
46
|
+
response.trans_init.should == '2004-08-3113:42:43'
|
47
|
+
response.trans_sent.should == '2004-08-3113:48:13'
|
48
|
+
response.trans_recv.should == ''
|
49
|
+
response.trans_cancel.should == ''
|
50
|
+
response.trans_auth_fraud.should == '0'
|
51
|
+
response.trans_ts.should == '1094205761232'
|
52
|
+
response.trans_sig.should == 'b6d68525f724a6d69fb1260874924759'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should parse error response" do
|
56
|
+
body = <<-EOF
|
57
|
+
status: ERROR
|
58
|
+
error_nr: 103
|
59
|
+
error_message: Kod błędu: 103
|
60
|
+
EOF
|
61
|
+
|
62
|
+
response = Payu::Response.parse(body)
|
63
|
+
|
64
|
+
response.status.should == 'ERROR'
|
65
|
+
response.error_nr.should == '103'
|
66
|
+
response.error_message.should == 'Kod błędu: 103'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should respond to completed?" do
|
70
|
+
response = Payu::Response.new(:trans_status => '99')
|
71
|
+
response.should be_completed
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Signature' do
|
6
|
+
before do
|
7
|
+
@expected_hash = 'e80b5017098950fc58aad83c8c14978e' # hash for 'abcdef'
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should generate MD5 hash for passed value' do
|
11
|
+
Payu::Signature.generate('abcdef').should == @expected_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should concatenate multiple values' do
|
15
|
+
Payu::Signature.generate('abc', 'def').should == @expected_hash
|
16
|
+
Payu::Signature.generate('ab' ,'cd', 'ef').should == @expected_hash
|
17
|
+
Payu::Signature.generate(%w{a b c d e f}).should == @expected_hash
|
18
|
+
|
19
|
+
Payu::Signature.generate('def', 'abc').should_not == @expected_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should skip empty strings' do
|
23
|
+
Payu::Signature.generate('abc', '', 'def').should == @expected_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should skip nils' do
|
27
|
+
Payu::Signature.generate('abc', nil, 'def').should == @expected_hash
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should do verify signature' do
|
31
|
+
lambda do
|
32
|
+
Payu::Signature.verify!(@expected_hash, 'abc', 'def')
|
33
|
+
end.should_not raise_exception
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should raise exception when signature is not valid' do
|
37
|
+
lambda do
|
38
|
+
Payu::Signature.verify!(@expected_hash, 'def', 'abc')
|
39
|
+
end.should raise_exception(Payu::SignatureInvalid)
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Transaction' do
|
6
|
+
before do
|
7
|
+
@pos1 = Payu::Pos.new(
|
8
|
+
:pos_id => 1,
|
9
|
+
:pos_auth_key => 'abcde',
|
10
|
+
:key1 => '3d91f185cacad7c1d830d1472dfaacc5',
|
11
|
+
:key2 => 'a747e4b3e49e17459a8a402518d36022'
|
12
|
+
)
|
13
|
+
|
14
|
+
@pos2 = Payu::Pos.new(
|
15
|
+
:pos_id => 1,
|
16
|
+
:pos_auth_key => 'abcde',
|
17
|
+
:key1 => '3d91f185cacad7c1d830d1472dfaacc5',
|
18
|
+
:key2 => 'a747e4b3e49e17459a8a402518d36022',
|
19
|
+
:add_signature => true
|
20
|
+
)
|
21
|
+
|
22
|
+
@sms_pos = Payu::Pos.new(
|
23
|
+
:pos_id => 1,
|
24
|
+
:pos_auth_key => 'abcde',
|
25
|
+
:key1 => '3d91f185cacad7c1d830d1472dfaacc5',
|
26
|
+
:key2 => 'a747e4b3e49e17459a8a402518d36022',
|
27
|
+
:add_signature => true,
|
28
|
+
:variant => 'sms'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise exception when required attributes are empty" do
|
33
|
+
lambda do
|
34
|
+
@pos1.new_transaction
|
35
|
+
end.should raise_exception(Payu::TransactionInvalid)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should generate session_id when empty' do
|
39
|
+
transaction = @pos1.new_transaction(
|
40
|
+
:amount => 100,
|
41
|
+
:desc => 'Description',
|
42
|
+
:first_name => 'John',
|
43
|
+
:last_name => 'Doe',
|
44
|
+
:email => 'john.doe@example.org',
|
45
|
+
:client_ip => '127.0.0.1'
|
46
|
+
)
|
47
|
+
|
48
|
+
transaction.session_id.should_not be_nil
|
49
|
+
transaction.session_id.class.should == Fixnum
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should not overwrite passed session_id' do
|
53
|
+
transaction = @pos1.new_transaction(
|
54
|
+
:session_id => 123,
|
55
|
+
:amount => 100,
|
56
|
+
:desc => 'Description',
|
57
|
+
:first_name => 'John',
|
58
|
+
:last_name => 'Doe',
|
59
|
+
:email => 'john.doe@example.org',
|
60
|
+
:client_ip => '127.0.0.1'
|
61
|
+
)
|
62
|
+
|
63
|
+
transaction.session_id.should == 123
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should generate signature' do
|
67
|
+
transaction = @pos2.new_transaction(
|
68
|
+
:session_id => '123',
|
69
|
+
:amount => 100,
|
70
|
+
:desc => 'Description',
|
71
|
+
:first_name => 'John',
|
72
|
+
:last_name => 'Doe',
|
73
|
+
:email => 'john.doe@example.org',
|
74
|
+
:client_ip => '127.0.0.1'
|
75
|
+
)
|
76
|
+
|
77
|
+
transaction.ts.should_not be_nil
|
78
|
+
transaction.ts.class.should == Fixnum
|
79
|
+
|
80
|
+
signature_keys = [1, '123', 'abcde', 100, 'Description', 'John', 'Doe', 'john.doe@example.org', '127.0.0.1', transaction.ts, '3d91f185cacad7c1d830d1472dfaacc5']
|
81
|
+
expected_signature = Digest::MD5.hexdigest(signature_keys.join)
|
82
|
+
|
83
|
+
transaction.sig.should == expected_signature
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should generate signature from all keys' do
|
87
|
+
transaction = @pos2.new_transaction(
|
88
|
+
:pos_auth_key => 'abcde',
|
89
|
+
:pay_type => 't',
|
90
|
+
:amount => 15000,
|
91
|
+
:desc => 'Testowa',
|
92
|
+
:desc2 => 'Szczegółowy opis',
|
93
|
+
:trsDesc => 'Dodatkowy opis dla banku',
|
94
|
+
:order_id => 69,
|
95
|
+
:first_name => 'Jan',
|
96
|
+
:last_name => 'Kowalski',
|
97
|
+
:payback_login => 'jankowalski',
|
98
|
+
:street => 'Warszawska',
|
99
|
+
:street_hn => '21',
|
100
|
+
:street_an => '18',
|
101
|
+
:city => 'Szczecin',
|
102
|
+
:post_code => '01-259',
|
103
|
+
:country => 'PL',
|
104
|
+
:email => 'jan.kowalski@example.org',
|
105
|
+
:phone => '505-606-100',
|
106
|
+
:language => 'pl',
|
107
|
+
:client_ip => '192.168.1.1'
|
108
|
+
)
|
109
|
+
|
110
|
+
signature_keys = [
|
111
|
+
1, 't', transaction.session_id, 'abcde', 15000, 'Testowa',
|
112
|
+
'Szczegółowy opis', 'Dodatkowy opis dla banku', 69, 'Jan', 'Kowalski',
|
113
|
+
'jankowalski', 'Warszawska', '21', '18', 'Szczecin', '01-259', 'PL',
|
114
|
+
'jan.kowalski@example.org', '505-606-100', 'pl', '192.168.1.1',
|
115
|
+
transaction.ts, '3d91f185cacad7c1d830d1472dfaacc5'
|
116
|
+
]
|
117
|
+
expected_signature = Digest::MD5.hexdigest(signature_keys.join)
|
118
|
+
|
119
|
+
transaction.sig.should == expected_signature
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should set amount_netto attribute for sms variant" do
|
123
|
+
transaction = @sms_pos.new_transaction(
|
124
|
+
:session_id => '123',
|
125
|
+
:amount => 100,
|
126
|
+
:desc => 'Description',
|
127
|
+
:first_name => 'John',
|
128
|
+
:last_name => 'Doe',
|
129
|
+
:email => 'john.doe@example.org',
|
130
|
+
:client_ip => '127.0.0.1'
|
131
|
+
)
|
132
|
+
|
133
|
+
transaction.amount_netto.should == 100
|
134
|
+
transaction.amount.should be_nil
|
135
|
+
end
|
136
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: payu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michał Młoźniak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Simple integration with PayU gateway
|
14
|
+
email: michal.mlozniak@visuality.pl
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- .gitignore
|
20
|
+
- .rspec
|
21
|
+
- Gemfile
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- init.rb
|
26
|
+
- lib/payu.rb
|
27
|
+
- lib/payu/errors.rb
|
28
|
+
- lib/payu/gateway.rb
|
29
|
+
- lib/payu/helpers.rb
|
30
|
+
- lib/payu/pos.rb
|
31
|
+
- lib/payu/response.rb
|
32
|
+
- lib/payu/signature.rb
|
33
|
+
- lib/payu/transaction.rb
|
34
|
+
- lib/payu/version.rb
|
35
|
+
- payu.gemspec
|
36
|
+
- spec/response_spec.rb
|
37
|
+
- spec/signature_spec.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
- spec/transaction_spec.rb
|
40
|
+
homepage: http://github.com/ronin/payu
|
41
|
+
licenses: []
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.0.3
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: Simple integration with PayU gateway
|
63
|
+
test_files:
|
64
|
+
- spec/response_spec.rb
|
65
|
+
- spec/signature_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
- spec/transaction_spec.rb
|