p24 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fe475892d3ce9320eda7573e4fbbf8b7296708d5505700ad80300bcae324f4c4
4
+ data.tar.gz: fcae282e5de5d184b27d24456aa53a2cd4996808fa9b76910597d4ba67156b66
5
+ SHA512:
6
+ metadata.gz: 1890ad413b885b269ad7db9afd04b795a27208c8c6d629539ffaaca431611c8a81fa3a94524146128375673ff392eda9823bae7a5bdb51f8d0375005e56a3813
7
+ data.tar.gz: 5875fbd4044a7f9e34dbdc0090a7a14e44dba89ab64072b92aaf6dcee83eb8c316bfc4abd9f771b6e01eee305295892c8c4770d424bc7b4845c92d005dc54831
data/.rubocop.yml ADDED
@@ -0,0 +1,33 @@
1
+ require:
2
+ - rubocop-minitest
3
+ - rubocop-rake
4
+ Style/Documentation:
5
+ Enabled: false
6
+ AllCops:
7
+ TargetRubyVersion: 3.2
8
+ NewCops: disable
9
+ Exclude:
10
+ - "bin/**/*"
11
+ - "vendor/**/*"
12
+ Metrics/MethodLength:
13
+ Enabled: false
14
+ Metrics/BlockLength:
15
+ Exclude:
16
+ - 'config/environments/**/*'
17
+ Security/MarshalLoad:
18
+ Enabled: false
19
+ Lint/MissingSuper:
20
+ Enabled: false
21
+ Style/FrozenStringLiteralComment:
22
+ Enabled: true
23
+ EnforcedStyle: always
24
+ Metrics/ClassLength:
25
+ Max: 150
26
+ Exclude:
27
+ - 'test/**/*'
28
+ Layout/LineLength:
29
+ Max: 150
30
+ Metrics/ParameterLists:
31
+ Enabled: false
32
+ Metrics/AbcSize:
33
+ Enabled: false
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Konrad Makowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Konrad Makowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # P24 == Przelewy24
2
+
3
+ A Ruby gem for integrating with Przelewy24 (P24), a popular Polish payment gateway. This library provides a simple and intuitive interface for handling online payments through the Przelewy24 API v1.
4
+
5
+ ## Features
6
+
7
+ - Transaction registration and verification
8
+ - Payment method discovery
9
+ - Transaction notification handling with signature verification
10
+ - Support for both sandbox and production environments
11
+ - Built-in authentication and request signing
12
+ - Comprehensive error handling
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'p24'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ Or install it yourself as:
29
+
30
+ ```bash
31
+ gem install p24
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ Initialize the client with your Przelewy24 credentials:
37
+
38
+ ```ruby
39
+ client = P24::Client.new(
40
+ user: 'your_merchant_id',
41
+ secret_id: 'your_api_secret',
42
+ crc: 'your_crc_key',
43
+ base_url: 'https://sandbox.przelewy24.pl/api/v1', # Use production URL for live transactions
44
+ timeout: 30,
45
+ encoding: 'UTF-8',
46
+ debug: false
47
+ )
48
+
49
+ # Alternatively, you can use the Przelewy24 alias
50
+ client = Przelewy24.new(
51
+ user: 'your_merchant_id',
52
+ secret_id: 'your_api_secret',
53
+ crc: 'your_crc_key'
54
+ )
55
+ ```
56
+
57
+ ### Configuration Options
58
+
59
+ - `user` (required): Your Przelewy24 merchant ID
60
+ - `secret_id` (required): Your API secret/password
61
+ - `crc` (required): Your CRC key for request signing
62
+ - `base_url` (optional): API endpoint URL (defaults to sandbox)
63
+ - `timeout` (optional): Request timeout in seconds (default: 30)
64
+ - `encoding` (optional): Character encoding (default: 'UTF-8')
65
+ - `debug` (optional): Enable debug output (default: false)
66
+
67
+ ## Usage
68
+
69
+ ### Test API Access
70
+
71
+ Verify your credentials and API connectivity:
72
+
73
+ ```ruby
74
+ response = client.test_access
75
+ puts response.error_code # Should be 0 for successful connection
76
+ ```
77
+
78
+ ### Register a Transaction
79
+
80
+ Create a new payment transaction:
81
+
82
+ ```ruby
83
+ response = client.transaction_register(
84
+ merchant_id: 12345,
85
+ pos_id: 12345,
86
+ session_id: 'unique_session_id',
87
+ amount: 1000, # Amount in grosze (1000 = 10.00 PLN)
88
+ currency: 'PLN',
89
+ description: 'Order #123',
90
+ email: 'customer@example.com',
91
+ country: 'PL',
92
+ language: 'pl',
93
+ url_return: 'https://yoursite.com/payment/return',
94
+ url_status: 'https://yoursite.com/payment/status'
95
+ )
96
+
97
+ # Get the payment token
98
+ token = response.token
99
+
100
+ # Redirect user to payment page
101
+ redirect_url = client.redirect_url(token)
102
+ # => "https://sandbox.przelewy24.pl/trnRequest/{token}"
103
+ ```
104
+
105
+ ### Verify a Transaction
106
+
107
+ Verify transaction status after payment:
108
+
109
+ ```ruby
110
+ response = client.transaction_verify(
111
+ merchant_id: 12345,
112
+ pos_id: 12345,
113
+ session_id: 'unique_session_id',
114
+ amount: 1000,
115
+ currency: 'PLN',
116
+ order_id: 98765 # Received from P24 notification
117
+ )
118
+
119
+ puts response.error_code # 0 means successful verification
120
+ ```
121
+
122
+ ### Handle Payment Notifications
123
+
124
+ Verify incoming payment notifications from Przelewy24:
125
+
126
+ ```ruby
127
+ # In your webhook/callback endpoint
128
+ is_valid = client.transaction_notification(
129
+ merchant_id: params[:merchantId],
130
+ pos_id: params[:posId],
131
+ session_id: params[:sessionId],
132
+ amount: params[:amount],
133
+ origin_amount: params[:originAmount],
134
+ currency: params[:currency],
135
+ order_id: params[:orderId],
136
+ method_id: params[:methodId],
137
+ statement: params[:statement],
138
+ sign: params[:sign]
139
+ )
140
+
141
+ if is_valid
142
+ # Process successful payment
143
+ else
144
+ # Invalid signature - potential fraud attempt
145
+ end
146
+ ```
147
+
148
+ ### Get Available Payment Methods
149
+
150
+ Retrieve available payment methods for your customers:
151
+
152
+ ```ruby
153
+ methods = client.payment_methods(
154
+ 'pl', # Language
155
+ amount: 1000, # Optional: filter by amount
156
+ currency: 'PLN' # Optional: filter by currency
157
+ )
158
+
159
+ methods.each do |method|
160
+ puts "#{method.name} - #{method.status}"
161
+ end
162
+ ```
163
+
164
+ ## API Reference
165
+
166
+ ### Client Methods
167
+
168
+ #### `transaction_register(**kwargs)`
169
+ Registers a new transaction and returns a token for payment redirection.
170
+
171
+ #### `transaction_verify(**kwargs)`
172
+ Verifies a completed transaction.
173
+
174
+ #### `transaction_notification(**kwargs)`
175
+ Validates the signature of a payment notification from P24.
176
+
177
+ #### `payment_methods(lang, amount: nil, currency: nil)`
178
+ Retrieves available payment methods.
179
+
180
+ #### `test_access`
181
+ Tests API credentials and connectivity.
182
+
183
+ #### `redirect_url(token)`
184
+ Generates the payment page URL for a given token.
185
+
186
+ ## Development
187
+
188
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
189
+
190
+ To install this gem onto your local machine, run `bundle exec rake install`.
191
+
192
+ ## Contributing
193
+
194
+ Bug reports and pull requests are welcome on GitHub at https://github.com/DeVeLo/p24.
195
+
196
+ ## License
197
+
198
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
199
+
200
+ ## Resources
201
+
202
+ - [Przelewy24 Official Documentation](https://www.przelewy24.pl/do-pobrania#dokumentacja-techniczna)
203
+ - [Przelewy24 API v1 Reference](https://developers.przelewy24.pl/)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Request
7
+ class TransactionRegister
8
+ attr_reader :merchant_id, :pos_id, :session_id, :amount, :currency, :description, :email,
9
+ :client, :address, :zip, :city, :country, :phone, :language, :method, :url_return,
10
+ :url_status, :time_limit, :channel, :wait_for_result, :regulation_accept, :shipping,
11
+ :transfer_label, :mobile_lib, :sdk_version, :encoding, :method_ref_id, :crc
12
+
13
+ def initialize(merchant_id:, pos_id:, session_id:, amount:, currency:, description:, email:,
14
+ country:, language:, url_return:, crc:, client: nil, address: nil, zip: nil, city: nil,
15
+ phone: nil, method: nil, url_status: nil, time_limit: nil, channel: nil,
16
+ wait_for_result: nil, regulation_accept: nil, shipping: nil, transfer_label: nil,
17
+ mobile_lib: nil, sdk_version: nil, encoding: nil, method_ref_id: nil)
18
+ @merchant_id = merchant_id
19
+ @pos_id = pos_id
20
+ @session_id = session_id
21
+ @amount = amount
22
+ @currency = currency
23
+ @description = description
24
+ @email = email
25
+ @client = client
26
+ @address = address
27
+ @zip = zip
28
+ @city = city
29
+ @country = country
30
+ @phone = phone
31
+ @language = language
32
+ @method = method
33
+ @url_return = url_return
34
+ @url_status = url_status
35
+ @time_limit = time_limit
36
+ @channel = channel
37
+ @wait_for_result = wait_for_result
38
+ @regulation_accept = regulation_accept
39
+ @shipping = shipping
40
+ @transfer_label = transfer_label
41
+ @mobile_lib = mobile_lib
42
+ @sdk_version = sdk_version
43
+ @encoding = encoding
44
+ @method_ref_id = method_ref_id
45
+ @crc = crc
46
+ end
47
+
48
+ def to_json(*_args)
49
+ {
50
+ merchantId: merchant_id,
51
+ posId: pos_id,
52
+ sessionId: session_id,
53
+ amount:,
54
+ currency:,
55
+ description:,
56
+ email:,
57
+ client:,
58
+ address:,
59
+ zip:,
60
+ city:,
61
+ country:,
62
+ phone:,
63
+ language:,
64
+ method:,
65
+ urlReturn: url_return,
66
+ urlStatus: url_status,
67
+ timeLimit: time_limit,
68
+ channel:,
69
+ waitForResult: wait_for_result,
70
+ regulationAccept: regulation_accept,
71
+ shipping:,
72
+ transferLabel: transfer_label,
73
+ mobileLib: mobile_lib,
74
+ sdkVersion: sdk_version,
75
+ sign:,
76
+ encoding:,
77
+ methodRefId: method_ref_id
78
+ }.compact.to_json
79
+ end
80
+
81
+ private
82
+
83
+ def sign_params
84
+ {
85
+ sessionId: session_id,
86
+ merchantId: merchant_id,
87
+ amount:,
88
+ currency:,
89
+ crc:
90
+ }
91
+ end
92
+
93
+ def sign
94
+ Digest::SHA384.hexdigest sign_params.to_json
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Request
7
+ class TransactionVerify
8
+ attr_reader :merchant_id, :pos_id, :session_id, :amount, :currency, :order_id, :crc
9
+
10
+ def initialize(merchant_id:, pos_id:, session_id:, amount:, currency:, order_id:, crc:)
11
+ @merchant_id = merchant_id
12
+ @pos_id = pos_id
13
+ @session_id = session_id
14
+ @amount = amount
15
+ @currency = currency
16
+ @order_id = order_id
17
+ @crc = crc
18
+ end
19
+
20
+ def to_json(*_args)
21
+ {
22
+ merchantId: merchant_id,
23
+ posId: pos_id,
24
+ orderId: order_id,
25
+ sessionId: session_id,
26
+ amount:,
27
+ currency:,
28
+ sign:
29
+ }.compact.to_json
30
+ end
31
+
32
+ private
33
+
34
+ def sign_params
35
+ {
36
+ sessionId: session_id,
37
+ orderId: order_id,
38
+ amount:,
39
+ currency:,
40
+ crc:
41
+ }
42
+ end
43
+
44
+ def sign
45
+ Digest::SHA384.hexdigest sign_params.to_json
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Response
7
+ module PaymentMethod
8
+ class AvailabilityHours
9
+ attr_reader :monday_to_friday, :saturday, :sunday
10
+
11
+ def initialize(monday_to_friday:, saturday:, sunday:)
12
+ @monday_to_friday = monday_to_friday
13
+ @saturday = saturday
14
+ @sunday = sunday
15
+ end
16
+
17
+ def to_json(options = {})
18
+ { monday_to_friday:, saturday:, sunday: }.to_json(options)
19
+ end
20
+
21
+ class << self
22
+ def from_json(json)
23
+ new(
24
+ monday_to_friday: json['mondayToFriday'],
25
+ saturday: json['saturday'],
26
+ sunday: json['sunday']
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Response
7
+ module PaymentMethod
8
+ class Data
9
+ attr_reader :name, :id, :group, :subgroup, :status, :img_url,
10
+ :mobile_img_url, :mobile, :availability_hours
11
+
12
+ def initialize(name:, id:, group:, subgroup:, status:, img_url:,
13
+ mobile_img_url:, mobile:, availability_hours:)
14
+ @name = name
15
+ @id = id
16
+ @group = group
17
+ @subgroup = subgroup
18
+ @status = status
19
+ @img_url = img_url
20
+ @mobile_img_url = mobile_img_url
21
+ @mobile = mobile
22
+ @availability_hours = AvailabilityHours.from_json(availability_hours)
23
+ end
24
+
25
+ def to_json(options = {})
26
+ { name:, id:, group:, subgroup:, status:, img_url:,
27
+ mobile_img_url:, mobile:, availability_hours: }.to_json(options)
28
+ end
29
+
30
+ class << self
31
+ def from_json(json)
32
+ new(
33
+ name: json['name'],
34
+ id: json['id'],
35
+ group: json['group'],
36
+ subgroup: json['subgroup'],
37
+ status: json['status'],
38
+ img_url: json['imgUrl'],
39
+ mobile_img_url: json['mobileImgUrl'],
40
+ mobile: json['mobile'],
41
+ availability_hours: json['availabilityHours']
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Response
7
+ class PaymentMethods
8
+ attr_reader :data, :agreements, :response_code
9
+
10
+ def initialize(data:, agreements:, response_code:)
11
+ @data = data.map { |d| PaymentMethod::Data.from_json(d) }
12
+ @agreements = agreements
13
+ @response_code = response_code
14
+ end
15
+
16
+ def to_json(options = {})
17
+ { data:, agreements:, response_code: }.to_json(options)
18
+ end
19
+
20
+ class << self
21
+ def from_json(json)
22
+ new(
23
+ data: json['data'],
24
+ agreements: json['agreements'],
25
+ response_code: json['responseCode']
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Response
7
+ class TestAccess
8
+ attr_reader :data, :error
9
+
10
+ def initialize(data:, error:)
11
+ @data = data
12
+ @error = error
13
+ end
14
+
15
+ def to_json(options = {})
16
+ { data:, error: }.to_json(options)
17
+ end
18
+
19
+ class << self
20
+ def from_json(json)
21
+ new(
22
+ data: json['data'],
23
+ error: json['error']
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Response
7
+ class TransactionRegister
8
+ attr_reader :data, :response_code
9
+
10
+ Data = Struct.new(:token)
11
+
12
+ def initialize(data:, response_code:)
13
+ @data = Data.new(data['token'])
14
+ @response_code = response_code
15
+ end
16
+
17
+ def to_json(options = {})
18
+ { data: data.to_h, response_code: }.to_json(options)
19
+ end
20
+
21
+ class << self
22
+ def from_json(json)
23
+ new(
24
+ data: json['data'],
25
+ response_code: json['responseCode']
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ module Response
7
+ class TransactionVerify
8
+ attr_reader :data, :response_code
9
+
10
+ Data = Struct.new(:status)
11
+
12
+ def initialize(data:, response_code:)
13
+ @data = Data.new(data['status'])
14
+ @response_code = response_code
15
+ end
16
+
17
+ def to_json(options = {})
18
+ { data: data.to_h, response_code: }.to_json(options)
19
+ end
20
+
21
+ class << self
22
+ def from_json(json)
23
+ new(
24
+ data: json['data'],
25
+ response_code: json['responseCode']
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ module Api
5
+ module V1
6
+ class TransactionNotification
7
+ attr_reader :merchant_id, :pos_id, :session_id, :amount, :origin_amount, :currency,
8
+ :order_id, :method_id, :statement, :crc, :sign
9
+
10
+ def initialize(merchant_id:, pos_id:, session_id:, amount:, origin_amount:, currency:,
11
+ order_id:, method_id:, statement:, crc:, sign: nil)
12
+ @merchant_id = merchant_id
13
+ @pos_id = pos_id
14
+ @session_id = session_id
15
+ @amount = amount
16
+ @origin_amount = origin_amount
17
+ @currency = currency
18
+ @order_id = order_id
19
+ @method_id = method_id
20
+ @statement = statement
21
+ @sign = sign
22
+ @crc = crc
23
+ end
24
+
25
+ def to_json(*_args)
26
+ {
27
+ merchantId: merchant_id,
28
+ posId: pos_id,
29
+ sessionId: session_id,
30
+ amount:,
31
+ originAmount: origin_amount,
32
+ currency:,
33
+ orderId: order_id,
34
+ methodId: method_id,
35
+ statement:,
36
+ sign:
37
+ }.compact.to_json
38
+ end
39
+
40
+ def from_json(json, crc)
41
+ new(
42
+ merchant_id: json['merchantId'],
43
+ pos_id: json['posId'],
44
+ session_id: json['sessionId'],
45
+ amount: ['amount'],
46
+ origin_amount: json['originAmount'],
47
+ currency: json['currency'],
48
+ order_id: json['orderId'],
49
+ method_id: json['methodId'],
50
+ statement: json['statement'],
51
+ sign: json['sign'],
52
+ crc:
53
+ )
54
+ end
55
+
56
+ def correct?
57
+ sign == Digest::SHA384.hexdigest(sign_params.to_json)
58
+ end
59
+
60
+ private
61
+
62
+ def sign_params
63
+ {
64
+ merchantId: merchant_id,
65
+ posId: pos_id,
66
+ sessionId: session_id,
67
+ amount:,
68
+ originAmount: origin_amount,
69
+ currency:,
70
+ orderId: order_id,
71
+ methodId: method_id,
72
+ statement:,
73
+ crc:
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
data/lib/p24/client.rb ADDED
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ class Client
5
+ attr_reader :user, :secret_id, :crc, :base_url, :timeout, :debug, :encoding
6
+
7
+ def initialize(user:, secret_id:, crc:, base_url: 'https://sandbox.przelewy24.pl/api/v1', timeout: 30,
8
+ encoding: 'UTF-8', debug: false)
9
+ @user = user
10
+ @secret_id = secret_id
11
+ @crc = crc
12
+ @timeout = timeout
13
+ @base_url = base_url
14
+ @debug = debug
15
+ @encoding = encoding
16
+ end
17
+
18
+ def transaction_register(**kwargs)
19
+ generic_post_json(
20
+ endpoint('/transaction/register'),
21
+ Api::V1::Request::TransactionRegister.new(**kwargs.merge({ crc:, encoding: })).to_json,
22
+ Api::V1::Response::TransactionRegister
23
+ )
24
+ end
25
+
26
+ def transaction_verify(**kwargs)
27
+ generic_put_json(
28
+ endpoint('/transaction/verify'),
29
+ Api::V1::Request::TransactionVerify.new(**kwargs.merge({ crc: })).to_json,
30
+ Api::V1::Response::TransactionVerify
31
+ )
32
+ end
33
+
34
+ def transaction_notification(**kwargs)
35
+ Api::V1::TransactionNotification.new(**kwargs.merge({ crc: })).correct?
36
+ end
37
+
38
+ def payment_methods(lang, amount: nil, currency: nil)
39
+ generic_json endpoint(
40
+ "/payment/methods/#{lang}",
41
+ **keyword_args(__method__, binding)
42
+ ), Api::V1::Response::PaymentMethods
43
+ end
44
+
45
+ def test_access
46
+ generic_json endpoint(
47
+ '/testAccess'
48
+ ), Api::V1::Response::TestAccess
49
+ end
50
+
51
+ def redirect_url(token)
52
+ "#{uri(base_url)}/trnRequest/#{token}"
53
+ end
54
+
55
+ private
56
+
57
+ def endpoint(url, *_args, **params)
58
+ uri = URI "#{base_url}#{url}"
59
+ uri.query = URI.encode_www_form(convert_keys_to_camelcase(params)).gsub('%2C', ',')
60
+ uri.to_s
61
+ end
62
+
63
+ def generic_json(uri, type)
64
+ response = HTTParty.get(uri, timeout:, basic_auth:, headers:, debug_output: debug ? $stdout : false)
65
+ handle_response(uri, response, type)
66
+ end
67
+
68
+ def generic_post_json(uri, body, type)
69
+ response = HTTParty.post(uri, timeout:, basic_auth:, headers:, body:, debug_output: debug ? $stdout : false)
70
+ handle_response(uri, response, type)
71
+ end
72
+
73
+ def generic_put_json(uri, body, type)
74
+ response = HTTParty.put(uri, timeout:, basic_auth:, headers:, body:, debug_output: debug ? $stdout : false)
75
+ handle_response(uri, response, type)
76
+ end
77
+
78
+ def handle_response(uri, response, type)
79
+ if response.success?
80
+ json_response = JSON.parse(response.body)
81
+ response_container = json_response.is_a?(Array) ? json_response : json_response['response']
82
+ if response_container.is_a?(Array)
83
+ response_container.map do |response_json|
84
+ type.from_json(response_json)
85
+ end
86
+ else
87
+ type.from_json(json_response)
88
+ end
89
+ else
90
+ raise_error(response, uri)
91
+ end
92
+ end
93
+
94
+ def raise_error(response, uri)
95
+ raise "Invalid HTTP response code: #{response.code}; " \
96
+ "Response body: #{response.body}; " \
97
+ "Request URI: #{uri};"
98
+ end
99
+
100
+ def convert_keys_to_camelcase(hash)
101
+ hash.transform_keys { |key| camelcase(key.to_s) }
102
+ end
103
+
104
+ def basic_auth
105
+ { username: user, password: secret_id }
106
+ end
107
+
108
+ def headers
109
+ { 'Content-Type' => 'application/json' }
110
+ end
111
+
112
+ def pids(array)
113
+ return if array.count.zero?
114
+
115
+ array.join(',')
116
+ end
117
+
118
+ def handle_args(arg)
119
+ case arg
120
+ when Array
121
+ pids(arg)
122
+ else
123
+ arg
124
+ end
125
+ end
126
+
127
+ def camelcase(str)
128
+ str.split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
129
+ end
130
+
131
+ def keyword_args(meth, bind)
132
+ method(meth).parameters.select { |type, _| %i[key keyreq].include? type }.map do |_, name|
133
+ [name, handle_args(bind.local_variable_get(name))]
134
+ end.to_h.compact
135
+ end
136
+
137
+ def uri(url)
138
+ purl = URI.parse(url)
139
+ "#{purl.scheme}://#{purl.host}"
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module P24
4
+ VERSION = '0.1.0'
5
+ end
data/lib/p24.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'uri'
5
+ require 'httparty'
6
+ require 'digest'
7
+
8
+ require_relative 'p24/version'
9
+ require_relative 'p24/client'
10
+
11
+ require_relative 'przelewy24'
12
+
13
+ require_relative 'p24/api/v1/response/test_access'
14
+
15
+ require_relative 'p24/api/v1/request/transaction_register'
16
+
17
+ require_relative 'p24/api/v1/response/payment_methods'
18
+ require_relative 'p24/api/v1/response/payment_method/data'
19
+ require_relative 'p24/api/v1/response/payment_method/availability_hours'
20
+ require_relative 'p24/api/v1/response/transaction_register'
21
+
22
+ require_relative 'p24/api/v1/request/transaction_verify'
23
+ require_relative 'p24/api/v1/response/transaction_verify'
24
+
25
+ require_relative 'p24/api/v1/transaction_notification'
26
+
27
+ module P24
28
+ end
data/lib/przelewy24.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Przelewy24 = P24::Client
data/sig/p24.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module P24
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: p24
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Konrad Makowski
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: httparty
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: A Ruby gem for integrating with Przelewy24 (P24), a popular Polish payment
27
+ gateway. Provides transaction registration, verification, payment method discovery,
28
+ and notification handling with signature verification.
29
+ email:
30
+ - konrad@snopkow.eu
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rubocop.yml"
36
+ - LICENSE
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - lib/p24.rb
41
+ - lib/p24/api/v1/request/transaction_register.rb
42
+ - lib/p24/api/v1/request/transaction_verify.rb
43
+ - lib/p24/api/v1/response/payment_method/availability_hours.rb
44
+ - lib/p24/api/v1/response/payment_method/data.rb
45
+ - lib/p24/api/v1/response/payment_methods.rb
46
+ - lib/p24/api/v1/response/test_access.rb
47
+ - lib/p24/api/v1/response/transaction_register.rb
48
+ - lib/p24/api/v1/response/transaction_verify.rb
49
+ - lib/p24/api/v1/transaction_notification.rb
50
+ - lib/p24/client.rb
51
+ - lib/p24/version.rb
52
+ - lib/przelewy24.rb
53
+ - sig/p24.rbs
54
+ homepage: https://github.com/DeVeLo/p24
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ homepage_uri: https://github.com/DeVeLo/p24
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.2.4
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 4.0.0
74
+ specification_version: 4
75
+ summary: Ruby client for Przelewy24 payment gateway API
76
+ test_files: []