payu_pl 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +78 -0
- data/config/locales/en.yml +1 -0
- data/config/locales/pl.yml +1 -0
- data/lib/payu_pl/client.rb +19 -0
- data/lib/payu_pl/contracts/order_create_contract.rb +10 -0
- data/lib/payu_pl/contracts/payout_create_contract.rb +24 -0
- data/lib/payu_pl/endpoints.rb +16 -0
- data/lib/payu_pl/payouts/create.rb +14 -0
- data/lib/payu_pl/payouts/retrieve.rb +12 -0
- data/lib/payu_pl/shops/retrieve.rb +12 -0
- data/lib/payu_pl/statements/retrieve.rb +33 -0
- data/lib/payu_pl/transport.rb +20 -4
- data/lib/payu_pl/version.rb +1 -1
- data/lib/payu_pl.rb +8 -0
- data/sig/payu_pl.rbs +8 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3153797e232dc9495c8f175f37558a8f360688b9f53e311cf925652a55db620
|
|
4
|
+
data.tar.gz: ca4dcc6531dec57bbc908c39cd5dd801d8fb9e5e33de22eca1df876e40abdd8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 144220b42c84c7745c1f41755afe6c118bf9b0d2b06567abdea30ab21403341c7991525f914e70da3f971781ae4944d66b22cb6826603c4fcf96f18a2c88f1c8
|
|
7
|
+
data.tar.gz: c7a8325b86ca1102977cbfbe776b6013c31c4a78ddf7f16d7401933aeebf881d8aa47ac427a47a3f9ab28ed7ff539e5f5f4cc56c0dce074a7a3af2cdbc712d64
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2025-12-31
|
|
4
|
+
|
|
5
|
+
- Add Shop Data endpoint: `GET /api/v2_1/shops/{shopId}` (`Client#retrieve_shop_data`)
|
|
6
|
+
- Add Payouts endpoints: `POST /api/v2_1/payouts`, `GET /api/v2_1/payouts/{payoutId}` (`Client#create_payout`, `Client#retrieve_payout`)
|
|
7
|
+
- Add Statements endpoint: `GET /api/v2_1/reports/{reportId}` (`Client#retrieve_statement`) returning binary `data` + extracted `filename`
|
|
8
|
+
- Extend `Transport#request` with `return_headers:` to expose response headers for binary downloads
|
|
9
|
+
- Update README and RBS, and add client specs for the new endpoints
|
|
10
|
+
|
|
3
11
|
## [0.1.0] - 2025-12-30
|
|
4
12
|
|
|
5
13
|
- Add i18n-backed validation messages (English + Polish) and `PayuPl.configure` locale support
|
data/README.md
CHANGED
|
@@ -14,6 +14,12 @@ This gem focuses on the standard payment flow endpoints:
|
|
|
14
14
|
- Refunds
|
|
15
15
|
- Transaction retrieve
|
|
16
16
|
|
|
17
|
+
Additional supported areas:
|
|
18
|
+
|
|
19
|
+
- Retrieve Shop Data
|
|
20
|
+
- Payouts
|
|
21
|
+
- Statements
|
|
22
|
+
|
|
17
23
|
## Installation
|
|
18
24
|
|
|
19
25
|
Add to your Gemfile:
|
|
@@ -106,6 +112,78 @@ client.retrieve_refund(order_id, "5000000142")
|
|
|
106
112
|
client.retrieve_transactions(order_id)
|
|
107
113
|
```
|
|
108
114
|
|
|
115
|
+
### Retrieve shop data
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
client.retrieve_shop_data("SHOP_ID")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Payouts
|
|
122
|
+
|
|
123
|
+
PayU supports multiple payout request schemas (Standard Payout, Bank Account Payout, Card Payout, Payout for Marketplace, FxPayout).
|
|
124
|
+
This client sends the JSON payload as-is, so your request must match one of the schemas from PayU docs.
|
|
125
|
+
|
|
126
|
+
Note: Payouts are a permissioned product in PayU. If your POS/shop is not enabled for payouts (common in sandbox or without the right agreement), PayU may respond with HTTP `403` (e.g. `ERROR_VALUE_INVALID` / "Permission denied for given action").
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# Standard Payout
|
|
130
|
+
standard_payout = {
|
|
131
|
+
shopId: "1a2B3Cx",
|
|
132
|
+
payout: {
|
|
133
|
+
extPayoutId: "payout-123",
|
|
134
|
+
amount: 10_000,
|
|
135
|
+
description: "Payout"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Bank Account Payout
|
|
140
|
+
bank_account_payout = {
|
|
141
|
+
shopId: "1a2B3Cx",
|
|
142
|
+
payout: { extPayoutId: "payout-124", amount: 10_000, description: "Payout" },
|
|
143
|
+
account: { accountNumber: "PL61109010140000071219812874" },
|
|
144
|
+
customerAddress: { name: "Jane Doe" }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Card Payout (use either cardToken or card)
|
|
148
|
+
card_payout = {
|
|
149
|
+
shopId: "1a2B3Cx",
|
|
150
|
+
payout: { extPayoutId: "payout-125", amount: 10_000, description: "Payout" },
|
|
151
|
+
payee: { extCustomerId: "customer-id-1", accountCreationDate: "2025-03-27T00:00:00.000Z", email: "email@email.com" },
|
|
152
|
+
customerAddress: { name: "Jane Doe" },
|
|
153
|
+
cardToken: "TOKC_..."
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Payout for Marketplace
|
|
157
|
+
marketplace_payout = {
|
|
158
|
+
shopId: "1a2B3Cx",
|
|
159
|
+
account: { extCustomerId: "submerchant1" },
|
|
160
|
+
payout: { extPayoutId: "payout-126", amount: 10_000, currencyCode: "PLN", description: "Payout" }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# FxPayout
|
|
164
|
+
fx_payout = {
|
|
165
|
+
shopId: "1a2B3Cx",
|
|
166
|
+
account: { extCustomerId: "submerchant1" },
|
|
167
|
+
payout: { extPayoutId: "payout-127", amount: 10_000, currencyCode: "PLN", description: "Payout" },
|
|
168
|
+
fxData: { partnerId: "...", currencyCode: "EUR", amount: 2500, rate: 0.25, tableId: "2055" }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
client.create_payout(standard_payout)
|
|
172
|
+
client.retrieve_payout("PAYOUT_ID")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Statements
|
|
176
|
+
|
|
177
|
+
This endpoint returns binary data and includes filename metadata from `Content-Disposition`.
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
statement = client.retrieve_statement("REPORT_ID")
|
|
181
|
+
# => { data: "...", filename: "...", content_type: "...", http_status: 200 }
|
|
182
|
+
|
|
183
|
+
safe_filename = File.basename(statement.fetch(:filename)).gsub(/[^0-9A-Za-z.\-]/, "_")
|
|
184
|
+
File.binwrite(safe_filename, statement.fetch(:data))
|
|
185
|
+
```
|
|
186
|
+
|
|
109
187
|
## Errors and validation
|
|
110
188
|
|
|
111
189
|
HTTP errors are mapped to Ruby exceptions:
|
data/config/locales/en.yml
CHANGED
data/config/locales/pl.yml
CHANGED
data/lib/payu_pl/client.rb
CHANGED
|
@@ -73,6 +73,25 @@ module PayuPl
|
|
|
73
73
|
Refunds::Retrieve.new(client: self).call(order_id, refund_id)
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
# Shops
|
|
77
|
+
def retrieve_shop_data(shop_id)
|
|
78
|
+
Shops::Retrieve.new(client: self).call(shop_id)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Payouts
|
|
82
|
+
def create_payout(payout_request)
|
|
83
|
+
Payouts::Create.new(client: self).call(payout_request)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def retrieve_payout(payout_id)
|
|
87
|
+
Payouts::Retrieve.new(client: self).call(payout_id)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Statements
|
|
91
|
+
def retrieve_statement(report_id)
|
|
92
|
+
Statements::Retrieve.new(client: self).call(report_id)
|
|
93
|
+
end
|
|
94
|
+
|
|
76
95
|
private
|
|
77
96
|
|
|
78
97
|
def validate!
|
|
@@ -25,10 +25,13 @@ module PayuPl
|
|
|
25
25
|
required(:currencyCode).filled(:string)
|
|
26
26
|
required(:totalAmount).filled(:string)
|
|
27
27
|
|
|
28
|
+
optional(:validityTime).filled(:string)
|
|
29
|
+
|
|
28
30
|
required(:products).array(:hash) do
|
|
29
31
|
required(:name).filled(:string)
|
|
30
32
|
required(:unitPrice).filled(:string)
|
|
31
33
|
required(:quantity).filled(:string)
|
|
34
|
+
optional(:virtual).filled(:bool)
|
|
32
35
|
end
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -96,6 +99,13 @@ module PayuPl
|
|
|
96
99
|
key.failure(PayuPl.t(:numeric_string)) unless value.match?(/\A\d+\z/)
|
|
97
100
|
end
|
|
98
101
|
|
|
102
|
+
rule(:validityTime) do
|
|
103
|
+
next if value.nil?
|
|
104
|
+
|
|
105
|
+
# OpenAPI: string containing seconds
|
|
106
|
+
key.failure(PayuPl.t(:numeric_string)) unless value.match?(/\A\d+\z/)
|
|
107
|
+
end
|
|
108
|
+
|
|
99
109
|
rule(:products) do
|
|
100
110
|
key.failure(PayuPl.t(:min_items, min: 1)) if value.nil? || value.empty?
|
|
101
111
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/validation"
|
|
4
|
+
|
|
5
|
+
module PayuPl
|
|
6
|
+
module Contracts
|
|
7
|
+
class PayoutCreateContract < Dry::Validation::Contract
|
|
8
|
+
params do
|
|
9
|
+
required(:payload).filled
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rule(:payload) do
|
|
13
|
+
val = value
|
|
14
|
+
|
|
15
|
+
unless val.is_a?(Hash)
|
|
16
|
+
key.failure(PayuPl.t(:hash))
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
key.failure(PayuPl.t(:min_items, min: 1)) if val.empty?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/payu_pl/endpoints.rb
CHANGED
|
@@ -8,6 +8,10 @@ module PayuPl
|
|
|
8
8
|
|
|
9
9
|
ORDERS = "/api/v2_1/orders"
|
|
10
10
|
|
|
11
|
+
SHOPS = "/api/v2_1/shops"
|
|
12
|
+
PAYOUTS = "/api/v2_1/payouts"
|
|
13
|
+
REPORTS = "/api/v2_1/reports"
|
|
14
|
+
|
|
11
15
|
def self.order(order_id)
|
|
12
16
|
"#{ORDERS}/#{URI.encode_www_form_component(order_id.to_s)}"
|
|
13
17
|
end
|
|
@@ -27,5 +31,17 @@ module PayuPl
|
|
|
27
31
|
def self.order_refund(order_id, refund_id)
|
|
28
32
|
"#{order_refunds(order_id)}/#{URI.encode_www_form_component(refund_id.to_s)}"
|
|
29
33
|
end
|
|
34
|
+
|
|
35
|
+
def self.shop(shop_id)
|
|
36
|
+
"#{SHOPS}/#{URI.encode_www_form_component(shop_id.to_s)}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.payout(payout_id)
|
|
40
|
+
"#{PAYOUTS}/#{URI.encode_www_form_component(payout_id.to_s)}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.report(report_id)
|
|
44
|
+
"#{REPORTS}/#{URI.encode_www_form_component(report_id.to_s)}"
|
|
45
|
+
end
|
|
30
46
|
end
|
|
31
47
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PayuPl
|
|
4
|
+
module Payouts
|
|
5
|
+
class Create < Operations::Base
|
|
6
|
+
def call(payout_request)
|
|
7
|
+
validate_contract!(Contracts::PayoutCreateContract, { payload: payout_request }, input: payout_request)
|
|
8
|
+
|
|
9
|
+
# Do NOT use result.to_h here: dry-schema would drop unknown keys, and PayU supports multiple payout schemas.
|
|
10
|
+
transport.request(:post, Endpoints::PAYOUTS, json: payout_request)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PayuPl
|
|
4
|
+
module Statements
|
|
5
|
+
class Retrieve < Operations::Base
|
|
6
|
+
FILENAME_REGEX = /filename="?(?<filename>[^";]+)"?/i.freeze
|
|
7
|
+
|
|
8
|
+
def call(report_id)
|
|
9
|
+
validate_id!(report_id, input_key: :report_id)
|
|
10
|
+
|
|
11
|
+
response = transport.request(
|
|
12
|
+
:get,
|
|
13
|
+
Endpoints.report(report_id),
|
|
14
|
+
headers: { "Accept" => "application/octet-stream" },
|
|
15
|
+
return_headers: true
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
headers = response.fetch(:headers)
|
|
19
|
+
content_type = headers["content-type"]
|
|
20
|
+
content_disposition = headers["content-disposition"].to_s
|
|
21
|
+
|
|
22
|
+
filename = content_disposition.match(FILENAME_REGEX)&.named_captures&.fetch("filename", nil)
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
data: response.fetch(:body),
|
|
26
|
+
filename: filename,
|
|
27
|
+
content_type: content_type,
|
|
28
|
+
http_status: response.fetch(:http_status)
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/payu_pl/transport.rb
CHANGED
|
@@ -15,7 +15,7 @@ module PayuPl
|
|
|
15
15
|
validate!
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def request(method, path, headers: {}, json: :__no_json_argument_given, form: nil, authorize: true)
|
|
18
|
+
def request(method, path, headers: {}, json: :__no_json_argument_given, form: nil, authorize: true, return_headers: false)
|
|
19
19
|
uri = URI.join(@base_url, path)
|
|
20
20
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
21
21
|
http.use_ssl = (uri.scheme == "https")
|
|
@@ -66,7 +66,7 @@ module PayuPl
|
|
|
66
66
|
raise NetworkError.new("Network failure", original: e)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
handle_response(res)
|
|
69
|
+
handle_response(res, return_headers: return_headers)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
private
|
|
@@ -78,13 +78,21 @@ module PayuPl
|
|
|
78
78
|
raise ArgumentError, "base_url is invalid"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
def handle_response(res)
|
|
81
|
+
def handle_response(res, return_headers: false)
|
|
82
82
|
http_status = res.code.to_i
|
|
83
83
|
correlation_id = res["Correlation-Id"] || res["correlation-id"]
|
|
84
84
|
raw_body = res.body
|
|
85
85
|
parsed = parse_body(res)
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
if http_status >= 200 && http_status < 400
|
|
88
|
+
return parsed unless return_headers
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
body: parsed,
|
|
92
|
+
headers: extract_headers(res),
|
|
93
|
+
http_status: http_status
|
|
94
|
+
}
|
|
95
|
+
end
|
|
88
96
|
|
|
89
97
|
message = build_error_message(http_status, parsed, raw_body)
|
|
90
98
|
|
|
@@ -107,6 +115,14 @@ module PayuPl
|
|
|
107
115
|
)
|
|
108
116
|
end
|
|
109
117
|
|
|
118
|
+
def extract_headers(res)
|
|
119
|
+
return {} unless res.respond_to?(:each_header)
|
|
120
|
+
|
|
121
|
+
headers = {}
|
|
122
|
+
res.each_header { |k, v| headers[k.to_s.downcase] = v }
|
|
123
|
+
headers
|
|
124
|
+
end
|
|
125
|
+
|
|
110
126
|
def parse_body(res)
|
|
111
127
|
body = res.body
|
|
112
128
|
return nil if body.nil? || body.empty?
|
data/lib/payu_pl/version.rb
CHANGED
data/lib/payu_pl.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "payu_pl/contracts/order_create_contract"
|
|
|
12
12
|
require_relative "payu_pl/contracts/id_contract"
|
|
13
13
|
require_relative "payu_pl/contracts/capture_contract"
|
|
14
14
|
require_relative "payu_pl/contracts/refund_create_contract"
|
|
15
|
+
require_relative "payu_pl/contracts/payout_create_contract"
|
|
15
16
|
|
|
16
17
|
require_relative "payu_pl/authorize/oauth_token"
|
|
17
18
|
|
|
@@ -24,6 +25,13 @@ require_relative "payu_pl/orders/transactions"
|
|
|
24
25
|
require_relative "payu_pl/refunds/create"
|
|
25
26
|
require_relative "payu_pl/refunds/list"
|
|
26
27
|
require_relative "payu_pl/refunds/retrieve"
|
|
28
|
+
|
|
29
|
+
require_relative "payu_pl/shops/retrieve"
|
|
30
|
+
|
|
31
|
+
require_relative "payu_pl/payouts/create"
|
|
32
|
+
require_relative "payu_pl/payouts/retrieve"
|
|
33
|
+
|
|
34
|
+
require_relative "payu_pl/statements/retrieve"
|
|
27
35
|
require_relative "payu_pl/client"
|
|
28
36
|
|
|
29
37
|
module PayuPl
|
data/sig/payu_pl.rbs
CHANGED
|
@@ -56,7 +56,7 @@ module PayuPl
|
|
|
56
56
|
|
|
57
57
|
class Transport
|
|
58
58
|
def initialize: (base_url: String, access_token_provider: ^() -> String?, ?open_timeout: Integer, ?read_timeout: Integer) -> void
|
|
59
|
-
def request: (untyped method, String path, ?headers: Hash[String, String], ?json: untyped, ?form: Hash[untyped, untyped]
|
|
59
|
+
def request: (untyped method, String path, ?headers: Hash[String, String], ?json: untyped, ?form: Hash[untyped, untyped]?, ?authorize: bool, ?return_headers: bool) -> untyped
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
class Client
|
|
@@ -90,5 +90,12 @@ module PayuPl
|
|
|
90
90
|
def retrieve_refund: (untyped order_id, untyped refund_id) -> untyped
|
|
91
91
|
|
|
92
92
|
def retrieve_transactions: (untyped order_id) -> untyped
|
|
93
|
+
|
|
94
|
+
def retrieve_shop_data: (untyped shop_id) -> untyped
|
|
95
|
+
|
|
96
|
+
def create_payout: (untyped payout_request) -> untyped
|
|
97
|
+
def retrieve_payout: (untyped payout_id) -> untyped
|
|
98
|
+
|
|
99
|
+
def retrieve_statement: (untyped report_id) -> untyped
|
|
93
100
|
end
|
|
94
101
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: payu_pl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmytro Koval
|
|
@@ -73,6 +73,7 @@ files:
|
|
|
73
73
|
- lib/payu_pl/contracts/capture_contract.rb
|
|
74
74
|
- lib/payu_pl/contracts/id_contract.rb
|
|
75
75
|
- lib/payu_pl/contracts/order_create_contract.rb
|
|
76
|
+
- lib/payu_pl/contracts/payout_create_contract.rb
|
|
76
77
|
- lib/payu_pl/contracts/refund_create_contract.rb
|
|
77
78
|
- lib/payu_pl/endpoints.rb
|
|
78
79
|
- lib/payu_pl/errors.rb
|
|
@@ -82,9 +83,13 @@ files:
|
|
|
82
83
|
- lib/payu_pl/orders/create.rb
|
|
83
84
|
- lib/payu_pl/orders/retrieve.rb
|
|
84
85
|
- lib/payu_pl/orders/transactions.rb
|
|
86
|
+
- lib/payu_pl/payouts/create.rb
|
|
87
|
+
- lib/payu_pl/payouts/retrieve.rb
|
|
85
88
|
- lib/payu_pl/refunds/create.rb
|
|
86
89
|
- lib/payu_pl/refunds/list.rb
|
|
87
90
|
- lib/payu_pl/refunds/retrieve.rb
|
|
91
|
+
- lib/payu_pl/shops/retrieve.rb
|
|
92
|
+
- lib/payu_pl/statements/retrieve.rb
|
|
88
93
|
- lib/payu_pl/transport.rb
|
|
89
94
|
- lib/payu_pl/version.rb
|
|
90
95
|
- sig/payu_pl.rbs
|