przelewy24_payment 0.2.0 → 2.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.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE.txt +17 -18
  4. data/README.md +345 -139
  5. data/app/controllers/przelewy24_payment/webhooks_controller.rb +63 -0
  6. data/app/jobs/przelewy24_payment/application_job.rb +6 -0
  7. data/app/jobs/przelewy24_payment/charge_due_subscriptions_job.rb +13 -0
  8. data/app/models/przelewy24_payment/application_record.rb +7 -0
  9. data/app/models/przelewy24_payment/payment.rb +177 -0
  10. data/app/models/przelewy24_payment/refund.rb +33 -0
  11. data/app/models/przelewy24_payment/subscription.rb +147 -0
  12. data/config/routes.rb +6 -0
  13. data/lib/generators/przelewy24_payment/install/install_generator.rb +60 -5
  14. data/lib/generators/przelewy24_payment/install/templates/README +14 -0
  15. data/lib/generators/przelewy24_payment/install/templates/create_przelewy24_payments.rb.tt +21 -0
  16. data/lib/generators/przelewy24_payment/install/templates/create_przelewy24_refunds.rb.tt +14 -0
  17. data/lib/generators/przelewy24_payment/install/templates/create_przelewy24_subscriptions.rb.tt +28 -0
  18. data/lib/generators/przelewy24_payment/install/templates/initializer.rb.tt +47 -0
  19. data/lib/przelewy24_payment/api/base.rb +17 -0
  20. data/lib/przelewy24_payment/api/blik.rb +41 -0
  21. data/lib/przelewy24_payment/api/cards.rb +26 -0
  22. data/lib/przelewy24_payment/api/payment_methods.rb +11 -0
  23. data/lib/przelewy24_payment/api/refunds.rb +25 -0
  24. data/lib/przelewy24_payment/api/transactions.rb +51 -0
  25. data/lib/przelewy24_payment/client.rb +66 -0
  26. data/lib/przelewy24_payment/configuration.rb +81 -0
  27. data/lib/przelewy24_payment/engine.rb +12 -1
  28. data/lib/przelewy24_payment/errors.rb +45 -0
  29. data/lib/przelewy24_payment/instrumentation.rb +16 -0
  30. data/lib/przelewy24_payment/money.rb +26 -0
  31. data/lib/przelewy24_payment/notification.rb +55 -0
  32. data/lib/przelewy24_payment/payable.rb +20 -0
  33. data/lib/przelewy24_payment/refund_notification.rb +44 -0
  34. data/lib/przelewy24_payment/signature.rb +64 -0
  35. data/lib/przelewy24_payment/test_helpers.rb +20 -0
  36. data/lib/przelewy24_payment/version.rb +3 -1
  37. data/lib/przelewy24_payment.rb +26 -187
  38. metadata +83 -24
  39. data/.gitignore +0 -1
  40. data/Gemfile +0 -4
  41. data/Rakefile +0 -2
  42. data/app/helpers/przelewy24_payment/payment_helper.rb +0 -5
  43. data/app/views/przelewy24_payment/_payment_form.html.haml +0 -28
  44. data/lib/generators/przelewy24_payment/install/USAGE +0 -8
  45. data/lib/generators/przelewy24_payment/install/templates/przelewy24_payment.rb +0 -16
  46. data/lib/przelewy24_payment/przelewy24_payment_controller.rb +0 -67
  47. data/lib/tasks/test_connection.rake +0 -18
  48. data/przelewy24_payment.gemspec +0 -19
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ module Api
5
+ class PaymentMethods < Base
6
+ def list(lang: config.default_language)
7
+ client.get("payment/methods/#{lang}").fetch("data")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ module Api
5
+ class Refunds < Base
6
+ # Refunds part or all of a verified transaction. Amount is in minor units.
7
+ def create(request_id:, order_id:, session_id:, amount:, url_status:, description: nil)
8
+ refunds_uuid = SecureRandom.uuid
9
+ body = {
10
+ requestId: request_id,
11
+ refunds: [ { orderId: order_id, sessionId: session_id, amount: amount, description: description }.compact ],
12
+ refundsUuid: refunds_uuid,
13
+ urlStatus: url_status
14
+ }
15
+
16
+ data = client.post("transaction/refund", body).fetch("data")
17
+ { "refundsUuid" => refunds_uuid, "refunds" => data }
18
+ end
19
+
20
+ def find(order_id)
21
+ client.get("refund/by/orderId/#{Integer(order_id)}").fetch("data")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ module Api
5
+ class Transactions < Base
6
+ # Registers a transaction and returns its token. Amount is in minor units.
7
+ # Extra keyword arguments are sent as-is in camelCase, e.g. method_ref_id:,
8
+ # method:, additional: { PSU: { IP:, userAgent: } }, transfer_label:,
9
+ # url_card_payment_notification:, time_limit:, wait_for_result:.
10
+ def register(session_id:, amount:, currency:, description:, email:, url_return:, url_status:, **extra)
11
+ body = {
12
+ merchantId: config.merchant_id,
13
+ posId: config.pos_id,
14
+ sessionId: session_id,
15
+ amount: amount,
16
+ currency: currency,
17
+ description: description,
18
+ email: email,
19
+ country: config.default_country,
20
+ language: config.default_language,
21
+ urlReturn: url_return,
22
+ urlStatus: url_status,
23
+ sign: Signature.register(session_id: session_id, merchant_id: config.merchant_id,
24
+ amount: amount, currency: currency, crc: config.crc_key)
25
+ }.merge(extra.transform_keys { |key| key.to_s.camelize(:lower).to_sym })
26
+
27
+ client.post("transaction/register", body).fetch("data").fetch("token")
28
+ end
29
+
30
+ # Confirms a transaction after a status notification. Required for settlement.
31
+ def verify(session_id:, order_id:, amount:, currency:)
32
+ body = {
33
+ merchantId: config.merchant_id,
34
+ posId: config.pos_id,
35
+ sessionId: session_id,
36
+ amount: amount,
37
+ currency: currency,
38
+ orderId: order_id,
39
+ sign: Signature.verify(session_id: session_id, order_id: order_id, amount: amount,
40
+ currency: currency, crc: config.crc_key)
41
+ }
42
+
43
+ client.put("transaction/verify", body).dig("data", "status") == "success"
44
+ end
45
+
46
+ def find_by_session_id(session_id)
47
+ client.get("transaction/by/sessionId/#{ERB::Util.url_encode(session_id)}").fetch("data")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module Przelewy24Payment
6
+ class Client
7
+ attr_reader :configuration
8
+
9
+ def initialize(configuration = Przelewy24Payment.configuration)
10
+ @configuration = configuration
11
+ end
12
+
13
+ def test_access
14
+ get("testAccess").dig("data") == true
15
+ end
16
+
17
+ def transactions = @transactions ||= Api::Transactions.new(self)
18
+ def refunds = @refunds ||= Api::Refunds.new(self)
19
+ def payment_methods = @payment_methods ||= Api::PaymentMethods.new(self)
20
+ def blik = @blik ||= Api::Blik.new(self)
21
+ def cards = @cards ||= Api::Cards.new(self)
22
+
23
+ def get(path, params = nil) = request(:get, path, params)
24
+ def post(path, body) = request(:post, path, body)
25
+ def put(path, body) = request(:put, path, body)
26
+
27
+ private
28
+
29
+ def request(verb, path, payload)
30
+ configuration.validate!
31
+ response = Instrumentation.instrument("request", verb: verb, path: path) do
32
+ connection.public_send(verb, path, payload)
33
+ end
34
+ handle(response)
35
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed, Faraday::SSLError => e
36
+ raise ConnectionError, "#{e.class}: #{e.message}"
37
+ end
38
+
39
+ def handle(response)
40
+ return response.body || {} if response.success?
41
+
42
+ body = response.body
43
+ message = body.is_a?(Hash) ? body["error"] : nil
44
+ message = message["message"] if message.is_a?(Hash)
45
+ error_class = case response.status
46
+ when 400, 422 then ValidationError
47
+ when 401, 403 then AuthenticationError
48
+ when 404 then NotFoundError
49
+ when 409 then ConflictError
50
+ when 500.. then ServerError
51
+ else ApiError
52
+ end
53
+ raise error_class.new(message ? "Przelewy24: #{message}" : nil, status: response.status, body: body)
54
+ end
55
+
56
+ def connection
57
+ @connection ||= Faraday.new(url: "#{configuration.api_url}/") do |conn|
58
+ conn.request :authorization, :basic, configuration.pos_id.to_s, configuration.api_key
59
+ conn.request :json
60
+ conn.response :json, content_type: /\bjson/
61
+ conn.options.open_timeout = configuration.open_timeout
62
+ conn.options.timeout = configuration.timeout
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ class Configuration
5
+ HOSTS = {
6
+ sandbox: "https://sandbox.przelewy24.pl",
7
+ production: "https://secure.przelewy24.pl"
8
+ }.freeze
9
+
10
+ ENV_KEYS = {
11
+ merchant_id: "PRZELEWY24_MERCHANT_ID",
12
+ pos_id: "PRZELEWY24_POS_ID",
13
+ api_key: "PRZELEWY24_API_KEY",
14
+ crc_key: "PRZELEWY24_CRC_KEY"
15
+ }.freeze
16
+
17
+ attr_writer :merchant_id, :pos_id, :api_key, :crc_key
18
+ attr_accessor :default_country, :default_language, :url_options,
19
+ :open_timeout, :timeout, :logger, :allowed_ips,
20
+ :on_payment_verified, :on_payment_failed, :on_refund,
21
+ :max_failed_attempts, :retry_interval, :parent_controller
22
+ attr_reader :environment
23
+
24
+ def initialize
25
+ self.environment = ENV.fetch("PRZELEWY24_ENVIRONMENT", "sandbox")
26
+ @default_country = "PL"
27
+ @default_language = "pl"
28
+ @url_options = {}
29
+ @open_timeout = 5
30
+ @timeout = 15
31
+ @allowed_ips = nil
32
+ @parent_controller = "ActionController::API"
33
+ @max_failed_attempts = 3
34
+ @retry_interval = 1.day
35
+ end
36
+
37
+ def environment=(value)
38
+ value = value.to_s.to_sym
39
+ unless HOSTS.key?(value)
40
+ raise ConfigurationError, "environment must be one of #{HOSTS.keys.inspect}, got #{value.inspect}"
41
+ end
42
+
43
+ @environment = value
44
+ end
45
+
46
+ def sandbox? = environment == :sandbox
47
+ def production? = environment == :production
48
+
49
+ def merchant_id = Integer(fetch!(:merchant_id))
50
+ def pos_id = Integer(value_for(:pos_id).presence || fetch!(:merchant_id))
51
+ def api_key = fetch!(:api_key).to_s
52
+ def crc_key = fetch!(:crc_key).to_s
53
+
54
+ def host = HOSTS.fetch(environment)
55
+ def api_url = "#{host}/api/v1"
56
+ def gateway_url(token) = "#{host}/trnRequest/#{token}"
57
+
58
+ def logger = @logger || (defined?(::Rails) ? ::Rails.logger : nil)
59
+
60
+ # Raises ConfigurationError naming every missing credential at once.
61
+ # Called lazily on first use, so asset precompilation and generators
62
+ # work without credentials.
63
+ def validate!
64
+ missing = ENV_KEYS.keys.reject { |key| key == :pos_id || value_for(key).present? }
65
+ return self if missing.empty?
66
+
67
+ names = missing.map { |key| "#{key} (#{ENV_KEYS.fetch(key)})" }
68
+ raise ConfigurationError, "Przelewy24Payment is missing: #{names.join(', ')}"
69
+ end
70
+
71
+ private
72
+
73
+ def fetch!(key)
74
+ value_for(key).presence || validate!.then { value_for(key) }
75
+ end
76
+
77
+ def value_for(key)
78
+ instance_variable_get("@#{key}").presence || ENV[ENV_KEYS.fetch(key)].presence
79
+ end
80
+ end
81
+ end
@@ -1,4 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Przelewy24Payment
2
- class Engine < Rails::Engine
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Przelewy24Payment
6
+
7
+ initializer "przelewy24_payment.filter_parameters" do |app|
8
+ app.config.filter_parameters += %i[sign crc crc_key api_key]
9
+ end
10
+
11
+ generators do
12
+ require "generators/przelewy24_payment/install/install_generator"
13
+ end
3
14
  end
4
15
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ class Error < StandardError; end
5
+
6
+ # Raised when a required setting is missing or invalid.
7
+ class ConfigurationError < Error; end
8
+
9
+ # Raised for transport-level failures (timeouts, DNS, TLS).
10
+ class ConnectionError < Error; end
11
+
12
+ # Raised when starting a payment for something that is already paid.
13
+ class AlreadyPaidError < Error; end
14
+
15
+ # Raised when a webhook signature does not match.
16
+ class SignatureError < Error; end
17
+
18
+ # Raised when a webhook payload lacks required fields.
19
+ class InvalidNotificationError < Error; end
20
+
21
+ # Raised when Przelewy24 answers with an error status.
22
+ class ApiError < Error
23
+ attr_reader :status, :body
24
+
25
+ def initialize(message = nil, status: nil, body: nil)
26
+ @status = status
27
+ @body = body
28
+ super(message || "Przelewy24 API error (HTTP #{status})")
29
+ end
30
+ end
31
+
32
+ class AuthenticationError < ApiError; end
33
+ class ValidationError < ApiError; end
34
+ class NotFoundError < ApiError; end
35
+ class ServerError < ApiError; end
36
+
37
+ class ConflictError < ApiError; end
38
+
39
+ # BLIK chargeByAlias answered 409: the alias is linked to several bank apps.
40
+ class AmbiguousAliasError < ConflictError
41
+ def alternative_keys
42
+ Array(body.is_a?(Hash) ? body.dig("error", "alternativeKeys") : nil)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+
5
+ module Przelewy24Payment
6
+ # Events are published as "<name>.przelewy24", e.g. "request.przelewy24".
7
+ module Instrumentation
8
+ NAMESPACE = "przelewy24"
9
+
10
+ module_function
11
+
12
+ def instrument(name, payload = {}, &block)
13
+ ActiveSupport::Notifications.instrument("#{name}.#{NAMESPACE}", payload, &block)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+
5
+ module Przelewy24Payment
6
+ # Converts between decimal amounts and the integer minor units
7
+ # (grosze, cents) that Przelewy24 expects.
8
+ module Money
9
+ module_function
10
+
11
+ def to_minor(amount)
12
+ case amount
13
+ when Integer then amount * 100
14
+ when BigDecimal then (amount * 100).round(0, :half_up).to_i
15
+ when String, Rational then (BigDecimal(amount.to_s) * 100).round(0, :half_up).to_i
16
+ when Float then (BigDecimal(amount.to_s) * 100).round(0, :half_up).to_i
17
+ else
18
+ raise ArgumentError, "unsupported amount: #{amount.inspect}"
19
+ end
20
+ end
21
+
22
+ def from_minor(minor)
23
+ BigDecimal(Integer(minor)) / 100
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ # A signed transaction status notification sent by Przelewy24 to urlStatus.
5
+ class Notification
6
+ REQUIRED = %w[merchantId posId sessionId amount currency orderId sign].freeze
7
+
8
+ attr_reader :attributes
9
+
10
+ def initialize(attributes, configuration: Przelewy24Payment.configuration)
11
+ @attributes = attributes.to_h.transform_keys(&:to_s)
12
+ @configuration = configuration
13
+ end
14
+
15
+ def self.from_json(raw, **options)
16
+ new(JSON.parse(raw), **options)
17
+ rescue JSON::ParserError => e
18
+ raise InvalidNotificationError, "malformed JSON: #{e.message}"
19
+ end
20
+
21
+ def session_id = attributes["sessionId"].to_s
22
+ def order_id = Integer(attributes["orderId"])
23
+ def amount = Integer(attributes["amount"])
24
+ def currency = attributes["currency"].to_s
25
+ def method_id = attributes["methodId"]&.then { |value| Integer(value) }
26
+ def statement = attributes["statement"]
27
+
28
+ def complete?
29
+ REQUIRED.all? { |key| attributes[key].to_s != "" }
30
+ end
31
+
32
+ def valid?
33
+ complete? &&
34
+ Integer(attributes["merchantId"]) == configuration.merchant_id &&
35
+ Signature.secure_compare?(expected_sign, attributes["sign"])
36
+ rescue ArgumentError, TypeError
37
+ false
38
+ end
39
+
40
+ def verify!
41
+ raise InvalidNotificationError, "missing required fields" unless complete?
42
+ raise SignatureError, "invalid notification signature" unless valid?
43
+
44
+ self
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :configuration
50
+
51
+ def expected_sign
52
+ Signature.notification(attributes, crc: configuration.crc_key)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ # Include in any model that can be paid for (Order, Invoice, ...).
5
+ module Payable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :przelewy24_payments, as: :payable, class_name: "Przelewy24Payment::Payment", dependent: :restrict_with_exception
10
+ end
11
+
12
+ def przelewy24_paid?
13
+ przelewy24_payments.verified.exists?
14
+ end
15
+
16
+ def latest_przelewy24_payment
17
+ przelewy24_payments.order(:id).last
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ # A signed refund status notification. Status 0 means the refund was
5
+ # completed, 1 means it was rejected.
6
+ class RefundNotification
7
+ REQUIRED = %w[orderId sessionId refundsUuid merchantId amount currency status sign].freeze
8
+
9
+ attr_reader :attributes
10
+
11
+ def initialize(attributes, configuration: Przelewy24Payment.configuration)
12
+ @attributes = attributes.to_h.transform_keys(&:to_s)
13
+ @configuration = configuration
14
+ end
15
+
16
+ def refunds_uuid = attributes["refundsUuid"].to_s
17
+ def session_id = attributes["sessionId"].to_s
18
+ def order_id = Integer(attributes["orderId"])
19
+ def amount = Integer(attributes["amount"])
20
+ def success? = Integer(attributes["status"]).zero?
21
+
22
+ def complete?
23
+ REQUIRED.all? { |key| attributes[key].to_s != "" }
24
+ end
25
+
26
+ def valid?
27
+ complete? &&
28
+ Signature.secure_compare?(Signature.refund_notification(attributes, crc: configuration.crc_key), attributes["sign"])
29
+ rescue ArgumentError, TypeError
30
+ false
31
+ end
32
+
33
+ def verify!
34
+ raise InvalidNotificationError, "missing required fields" unless complete?
35
+ raise SignatureError, "invalid refund notification signature" unless valid?
36
+
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :configuration
43
+ end
44
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+
6
+ module Przelewy24Payment
7
+ # Przelewy24 signs the compact JSON of an ordered hash with SHA-384.
8
+ # Key order is part of the contract, so every endpoint has its own builder.
9
+ module Signature
10
+ module_function
11
+
12
+ def register(session_id:, merchant_id:, amount:, currency:, crc:)
13
+ sign("sessionId" => session_id.to_s, "merchantId" => Integer(merchant_id),
14
+ "amount" => Integer(amount), "currency" => currency.to_s, "crc" => crc.to_s)
15
+ end
16
+
17
+ def verify(session_id:, order_id:, amount:, currency:, crc:)
18
+ sign("sessionId" => session_id.to_s, "orderId" => Integer(order_id),
19
+ "amount" => Integer(amount), "currency" => currency.to_s, "crc" => crc.to_s)
20
+ end
21
+
22
+ # Optional fields are signed only when Przelewy24 sends them, in the
23
+ # order below.
24
+ def notification(attrs, crc:)
25
+ fields = {}
26
+ fields["merchantId"] = Integer(attrs.fetch("merchantId"))
27
+ fields["posId"] = Integer(attrs.fetch("posId"))
28
+ fields["sessionId"] = attrs.fetch("sessionId").to_s
29
+ fields["amount"] = Integer(attrs.fetch("amount"))
30
+ fields["originAmount"] = Integer(attrs["originAmount"]) if present?(attrs["originAmount"])
31
+ fields["currency"] = attrs.fetch("currency").to_s
32
+ fields["orderId"] = Integer(attrs.fetch("orderId"))
33
+ fields["methodId"] = Integer(attrs["methodId"]) if present?(attrs["methodId"])
34
+ fields["statement"] = attrs["statement"].to_s if present?(attrs["statement"])
35
+ fields["crc"] = crc.to_s
36
+ sign(fields)
37
+ end
38
+
39
+ def refund_notification(attrs, crc:)
40
+ sign("orderId" => Integer(attrs.fetch("orderId")),
41
+ "sessionId" => attrs.fetch("sessionId").to_s,
42
+ "refundsUuid" => attrs.fetch("refundsUuid").to_s,
43
+ "merchantId" => Integer(attrs.fetch("merchantId")),
44
+ "amount" => Integer(attrs.fetch("amount")),
45
+ "currency" => attrs.fetch("currency").to_s,
46
+ "status" => Integer(attrs.fetch("status")),
47
+ "crc" => crc.to_s)
48
+ end
49
+
50
+ def sign(fields)
51
+ Digest::SHA384.hexdigest(JSON.generate(fields))
52
+ end
53
+
54
+ def present?(value)
55
+ !value.nil? && value.to_s != ""
56
+ end
57
+
58
+ def secure_compare?(expected, actual)
59
+ return false unless actual.is_a?(String) && actual.bytesize == expected.bytesize
60
+
61
+ OpenSSL.fixed_length_secure_compare(expected, actual)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Przelewy24Payment
4
+ # Builds signed webhook payloads for host-app specs.
5
+ # post przelewy24_payment.status_webhooks_path,
6
+ # params: Przelewy24Payment::TestHelpers.status_payload(payment), as: :json
7
+ module TestHelpers
8
+ module_function
9
+
10
+ def status_payload(payment, order_id: 123_456, method_id: 25, configuration: Przelewy24Payment.configuration)
11
+ attrs = {
12
+ "merchantId" => configuration.merchant_id, "posId" => configuration.pos_id,
13
+ "sessionId" => payment.session_id, "amount" => payment.amount,
14
+ "originAmount" => payment.amount, "currency" => payment.currency,
15
+ "orderId" => order_id, "methodId" => method_id, "statement" => "p24-#{order_id}"
16
+ }
17
+ attrs.merge("sign" => Signature.notification(attrs, crc: configuration.crc_key))
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Przelewy24Payment
2
- VERSION = "0.2.0"
4
+ VERSION = "2.0.0"
3
5
  end