nowpayments-ruby 1.0.0 → 1.0.5

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.
@@ -1,21 +1,21 @@
1
- # frozen_string_literal: true
2
-
3
- module NowPayments
4
- class NowPaymentsError < StandardError
5
- attr_reader :status_code, :code, :response
6
-
7
- def initialize(message, status_code = nil, code = nil, response = nil)
8
- super(message)
9
- @status_code = status_code
10
- @code = code
11
- @response = response
12
- end
13
-
14
- def to_s
15
- parts = [message]
16
- parts << "(status: #{status_code})" if status_code
17
- parts << "[#{code}]" if code
18
- parts.join(" ")
19
- end
20
- end
21
- end
1
+ # frozen_string_literal: true
2
+
3
+ module NowPayments
4
+ class NowPaymentsError < StandardError
5
+ attr_reader :status_code, :code, :response
6
+
7
+ def initialize(message, status_code = nil, code = nil, response = nil)
8
+ super(message)
9
+ @status_code = status_code
10
+ @code = code
11
+ @response = response
12
+ end
13
+
14
+ def to_s
15
+ parts = [message]
16
+ parts << "(status: #{status_code})" if status_code
17
+ parts << "[#{code}]" if code
18
+ parts.join(" ")
19
+ end
20
+ end
21
+ end
@@ -1,44 +1,44 @@
1
- # frozen_string_literal: true
2
-
3
- module NowPayments
4
- # Human-friendly helpers for payment status and display
5
- module Helpers
6
- # Check if payment is complete (success or terminal state)
7
- #
8
- # @param status [String] Payment status
9
- # @return [Boolean]
10
- def self.payment_complete?(status)
11
- PAYMENT_DONE_STATUSES.include?(status.to_s)
12
- end
13
-
14
- # Check if payment is still pending (customer should pay)
15
- #
16
- # @param status [String] Payment status
17
- # @return [Boolean]
18
- def self.payment_pending?(status)
19
- PAYMENT_PENDING_STATUSES.include?(status.to_s)
20
- end
21
-
22
- # Get human-readable status label
23
- #
24
- # @param status [String] Payment status
25
- # @return [String]
26
- def self.status_label(status)
27
- PAYMENT_STATUS_LABELS[status.to_s] || status.to_s
28
- end
29
-
30
- # Build a short summary for displaying to users
31
- # e.g. "Awaiting payment: 0.001234 BTC → bc1q..."
32
- #
33
- # @param payment [Hash] Payment object with pay_amount, pay_currency, pay_address, payment_status
34
- # @return [String]
35
- def self.payment_summary(payment)
36
- pay_amount = payment["pay_amount"] || payment[:pay_amount]
37
- pay_currency = (payment["pay_currency"] || payment[:pay_currency] || "").to_s.upcase
38
- pay_address = payment["pay_address"] || payment[:pay_address] || "…"
39
- payment_status = payment["payment_status"] || payment[:payment_status]
40
- label = PAYMENT_STATUS_LABELS[payment_status.to_s] || payment_status.to_s
41
- "#{label}: #{pay_amount} #{pay_currency} → #{pay_address}"
42
- end
43
- end
44
- end
1
+ # frozen_string_literal: true
2
+
3
+ module NowPayments
4
+ # Human-friendly helpers for payment status and display
5
+ module Helpers
6
+ # Check if payment is complete (success or terminal state)
7
+ #
8
+ # @param status [String] Payment status
9
+ # @return [Boolean]
10
+ def self.payment_complete?(status)
11
+ PAYMENT_DONE_STATUSES.include?(status.to_s)
12
+ end
13
+
14
+ # Check if payment is still pending (customer should pay)
15
+ #
16
+ # @param status [String] Payment status
17
+ # @return [Boolean]
18
+ def self.payment_pending?(status)
19
+ PAYMENT_PENDING_STATUSES.include?(status.to_s)
20
+ end
21
+
22
+ # Get human-readable status label
23
+ #
24
+ # @param status [String] Payment status
25
+ # @return [String]
26
+ def self.status_label(status)
27
+ PAYMENT_STATUS_LABELS[status.to_s] || status.to_s
28
+ end
29
+
30
+ # Build a short summary for displaying to users
31
+ # e.g. "Awaiting payment: 0.001234 BTC → bc1q..."
32
+ #
33
+ # @param payment [Hash] Payment object with pay_amount, pay_currency, pay_address, payment_status
34
+ # @return [String]
35
+ def self.payment_summary(payment)
36
+ pay_amount = payment["pay_amount"] || payment[:pay_amount]
37
+ pay_currency = (payment["pay_currency"] || payment[:pay_currency] || "").to_s.upcase
38
+ pay_address = payment["pay_address"] || payment[:pay_address] || "…"
39
+ payment_status = payment["payment_status"] || payment[:payment_status]
40
+ label = PAYMENT_STATUS_LABELS[payment_status.to_s] || payment_status.to_s
41
+ "#{label}: #{pay_amount} #{pay_currency} → #{pay_address}"
42
+ end
43
+ end
44
+ end
@@ -1,94 +1,94 @@
1
- # frozen_string_literal: true
2
-
3
- require "faraday"
4
- require "faraday/json"
5
-
6
- module NowPayments
7
- class Http
8
- def initialize(config)
9
- @config = config
10
- @conn = build_connection
11
- end
12
-
13
- def get(path, params = {}, jwt_token = nil)
14
- response = @conn.get(path) do |req|
15
- req.params = params unless params.empty?
16
- req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
17
- end
18
- handle_response(response)
19
- rescue Faraday::Error => e
20
- raise NowPaymentsError.new(
21
- e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
22
- nil, e.class.name, e
23
- )
24
- end
25
-
26
- def post(path, body = nil, jwt_token = nil)
27
- response = @conn.post(path) do |req|
28
- req.body = body.nil? ? {} : body
29
- req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
30
- end
31
- handle_response(response)
32
- rescue Faraday::Error => e
33
- raise NowPaymentsError.new(
34
- e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
35
- nil, e.class.name, e
36
- )
37
- end
38
-
39
- def patch(path, body, jwt_token = nil)
40
- response = @conn.patch(path) do |req|
41
- req.body = body
42
- req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
43
- end
44
- handle_response(response)
45
- rescue Faraday::Error => e
46
- raise NowPaymentsError.new(
47
- e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
48
- nil, e.class.name, e
49
- )
50
- end
51
-
52
- def delete(path, jwt_token = nil)
53
- response = @conn.delete(path) do |req|
54
- req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
55
- end
56
- handle_response(response)
57
- rescue Faraday::Error => e
58
- raise NowPaymentsError.new(
59
- e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
60
- nil, e.class.name, e
61
- )
62
- end
63
-
64
- private
65
-
66
- def build_connection
67
- base_url = @config[:base_url] || (@config[:sandbox] ? SANDBOX_URL : PRODUCTION_URL)
68
- timeout = @config[:timeout] || 30.0
69
-
70
- Faraday.new(url: base_url) do |f|
71
- f.request :json
72
- f.response :json, content_type: /\bjson$/
73
- f.options.timeout = timeout
74
- f.headers["Content-Type"] = "application/json"
75
- f.headers["x-api-key"] = @config[:api_key]
76
- f.adapter Faraday.default_adapter
77
- end
78
- end
79
-
80
- def handle_response(response)
81
- return response.body if response.success?
82
-
83
- data = response.body
84
- message = data.is_a?(Hash) ? (data["message"] || data["msg"] || data["error"]) : nil
85
- message ||= response.reason_phrase || "Request failed"
86
- raise NowPaymentsError.new(
87
- message,
88
- response.status,
89
- data.is_a?(Hash) ? data["code"] : nil,
90
- data
91
- )
92
- end
93
- end
94
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/json"
5
+
6
+ module NowPayments
7
+ class Http
8
+ def initialize(config)
9
+ @config = config
10
+ @conn = build_connection
11
+ end
12
+
13
+ def get(path, params = {}, jwt_token = nil)
14
+ response = @conn.get(path) do |req|
15
+ req.params = params unless params.empty?
16
+ req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
17
+ end
18
+ handle_response(response)
19
+ rescue Faraday::Error => e
20
+ raise NowPaymentsError.new(
21
+ e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
22
+ nil, e.class.name, e
23
+ )
24
+ end
25
+
26
+ def post(path, body = nil, jwt_token = nil)
27
+ response = @conn.post(path) do |req|
28
+ req.body = body unless body.nil?
29
+ req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
30
+ end
31
+ handle_response(response)
32
+ rescue Faraday::Error => e
33
+ raise NowPaymentsError.new(
34
+ e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
35
+ nil, e.class.name, e
36
+ )
37
+ end
38
+
39
+ def patch(path, body, jwt_token = nil)
40
+ response = @conn.patch(path) do |req|
41
+ req.body = body
42
+ req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
43
+ end
44
+ handle_response(response)
45
+ rescue Faraday::Error => e
46
+ raise NowPaymentsError.new(
47
+ e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
48
+ nil, e.class.name, e
49
+ )
50
+ end
51
+
52
+ def delete(path, jwt_token = nil)
53
+ response = @conn.delete(path) do |req|
54
+ req.headers["Authorization"] = "Bearer #{jwt_token}" if jwt_token.to_s.strip != ""
55
+ end
56
+ handle_response(response)
57
+ rescue Faraday::Error => e
58
+ raise NowPaymentsError.new(
59
+ e.is_a?(Faraday::TimeoutError) ? "Request timed out. Check your connection or try again." : (e.message || "Network error. Check your connection."),
60
+ nil, e.class.name, e
61
+ )
62
+ end
63
+
64
+ private
65
+
66
+ def build_connection
67
+ base_url = @config[:base_url] || (@config[:sandbox] ? SANDBOX_URL : PRODUCTION_URL)
68
+ timeout = @config[:timeout] || 30.0
69
+
70
+ Faraday.new(url: base_url) do |f|
71
+ f.request :json
72
+ f.response :json, content_type: /\bjson$/
73
+ f.options.timeout = timeout
74
+ f.headers["Content-Type"] = "application/json"
75
+ f.headers["x-api-key"] = @config[:api_key]
76
+ f.adapter Faraday.default_adapter
77
+ end
78
+ end
79
+
80
+ def handle_response(response)
81
+ return response.body if response.success?
82
+
83
+ data = response.body
84
+ message = data.is_a?(Hash) ? (data["message"] || data["msg"] || data["error"]) : nil
85
+ message ||= response.reason_phrase || "Request failed"
86
+ raise NowPaymentsError.new(
87
+ message,
88
+ response.status,
89
+ data.is_a?(Hash) ? data["code"] : nil,
90
+ data
91
+ )
92
+ end
93
+ end
94
+ end
@@ -1,63 +1,72 @@
1
- # frozen_string_literal: true
2
-
3
- module NowPayments
4
- # IPN (Instant Payment Notification) verification utilities
5
- # Matches official docs: sort keys recursively, then HMAC-SHA512
6
- # @see https://nowpayments.io/help/payments/api
7
- module IPN
8
- # Recursively sort object keys (matches NOWPayments IPN spec).
9
- # Normalizes to string keys for consistent JSON output (matches Node.js JSON.parse behavior).
10
- def self.sort_object(obj)
11
- return obj unless obj.is_a?(Hash)
12
-
13
- normalized = obj.transform_keys(&:to_s)
14
- normalized.keys.sort.each_with_object({}) do |key, result|
15
- val = normalized[key]
16
- result[key] = val.is_a?(Hash) && !val.is_a?(Array) ? sort_object(val) : val
17
- result
18
- end
19
- end
20
-
21
- # Verify IPN callback signature from NOWPayments.
22
- # Safe to call – handles invalid input gracefully.
23
- #
24
- # @param payload [String, Hash] Raw request body (string or parsed object)
25
- # @param signature [String] Value from x-nowpayments-sig header
26
- # @param ipn_secret [String] Your IPN Secret from Dashboard → Store Settings
27
- # @return [Boolean] true if signature is valid, false otherwise
28
- def self.verify_signature(payload, signature, ipn_secret)
29
- return false if signature.nil? || signature.to_s.strip.empty?
30
- return false if ipn_secret.nil? || ipn_secret.to_s.strip.empty?
31
-
32
- obj = case payload
33
- when String
34
- JSON.parse(payload)
35
- when Hash
36
- payload
37
- else
38
- return false
39
- end
40
-
41
- json_string = JSON.generate(sort_object(obj))
42
- computed_sig = OpenSSL::HMAC.hexdigest('SHA512', ipn_secret.strip, json_string)
43
-
44
- sig_bytes = [signature].pack('H*')
45
- computed_bytes = [computed_sig].pack('H*')
46
- return false if sig_bytes.bytesize != computed_bytes.bytesize
47
-
48
- OpenSSL::fixed_length_secure_compare(sig_bytes, computed_bytes)
49
- rescue JSON::ParserError, ArgumentError
50
- false
51
- end
52
-
53
- # Create IPN signature for testing (e.g., mocking callbacks)
54
- #
55
- # @param payload [Hash] Payload to sign
56
- # @param ipn_secret [String] IPN secret
57
- # @return [String] Hex-encoded HMAC-SHA512 signature
58
- def self.create_signature(payload, ipn_secret)
59
- json_string = JSON.generate(sort_object(payload))
60
- OpenSSL::HMAC.hexdigest('SHA512', ipn_secret.strip, json_string)
61
- end
62
- end
63
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "openssl"
5
+
6
+ module NowPayments
7
+ # IPN (Instant Payment Notification) verification utilities
8
+ # Matches official docs: sort keys recursively, then HMAC-SHA512
9
+ # @see https://nowpayments.io/help/payments/api
10
+ module IPN
11
+ # Recursively sort object keys (matches NOWPayments IPN spec).
12
+ # Normalizes to string keys for consistent JSON output (matches Node.js JSON.parse behavior).
13
+ def self.sort_object(obj)
14
+ return obj unless obj.is_a?(Hash)
15
+
16
+ normalized = obj.transform_keys(&:to_s)
17
+ normalized.keys.sort.each_with_object({}) do |key, result|
18
+ val = normalized[key]
19
+ result[key] = val.is_a?(Hash) ? sort_object(val) : val
20
+ result
21
+ end
22
+ end
23
+
24
+ # Verify IPN callback signature from NOWPayments.
25
+ # Safe to call – handles invalid input gracefully.
26
+ #
27
+ # Prefer the raw HTTP body string when possible. Framework-parsed hashes can
28
+ # coerce number/string types and break verification.
29
+ #
30
+ # @param payload [String, Hash] Raw request body (string recommended) or parsed object
31
+ # @param signature [String] Value from x-nowpayments-sig header
32
+ # @param ipn_secret [String] Your IPN Secret from Dashboard → Store Settings
33
+ # @return [Boolean] true if signature is valid, false otherwise
34
+ def self.verify_signature(payload, signature, ipn_secret)
35
+ return false if signature.nil? || signature.to_s.strip.empty?
36
+ return false if ipn_secret.nil? || ipn_secret.to_s.strip.empty?
37
+
38
+ obj = case payload
39
+ when String
40
+ JSON.parse(payload)
41
+ when Hash
42
+ payload
43
+ else
44
+ return false
45
+ end
46
+
47
+ json_string = JSON.generate(sort_object(obj))
48
+ computed_sig = OpenSSL::HMAC.hexdigest('SHA512', ipn_secret.strip, json_string)
49
+
50
+ sig_hex = signature.to_s.strip.downcase
51
+ sig_hex = sig_hex.split("=", 2).last if sig_hex.include?("=")
52
+ sig_hex = sig_hex.gsub(/[^a-f0-9]/, "")
53
+ sig_bytes = [sig_hex].pack('H*')
54
+ computed_bytes = [computed_sig.downcase].pack('H*')
55
+ return false if sig_bytes.bytesize != computed_bytes.bytesize || sig_bytes.empty?
56
+
57
+ OpenSSL::fixed_length_secure_compare(sig_bytes, computed_bytes)
58
+ rescue JSON::ParserError, ArgumentError
59
+ false
60
+ end
61
+
62
+ # Create IPN signature for testing (e.g., mocking callbacks)
63
+ #
64
+ # @param payload [Hash] Payload to sign
65
+ # @param ipn_secret [String] IPN secret
66
+ # @return [String] Hex-encoded HMAC-SHA512 signature
67
+ def self.create_signature(payload, ipn_secret)
68
+ json_string = JSON.generate(sort_object(payload))
69
+ OpenSSL::HMAC.hexdigest('SHA512', ipn_secret.strip, json_string)
70
+ end
71
+ end
72
+ end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module NowPayments
4
- VERSION = "1.0.0"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module NowPayments
4
+ VERSION = "1.0.5"
5
+ end
data/lib/nowpayments.rb CHANGED
@@ -1,22 +1,20 @@
1
- # frozen_string_literal: true
2
-
3
- require "nowpayments/version"
4
- require "nowpayments/constants"
5
- require "nowpayments/error"
6
- require "nowpayments/http"
7
- require "nowpayments/ipn"
8
- require "nowpayments/helpers"
9
- require "nowpayments/client"
10
-
11
- module NowPayments
12
- class Error < StandardError; end
13
-
14
- # Convenience aliases (Node.js style)
15
- def self.verify_ipn_signature(payload, signature, ipn_secret)
16
- IPN.verify_signature(payload, signature, ipn_secret)
17
- end
18
-
19
- def self.create_ipn_signature(payload, ipn_secret)
20
- IPN.create_signature(payload, ipn_secret)
21
- end
22
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "nowpayments/version"
4
+ require "nowpayments/constants"
5
+ require "nowpayments/error"
6
+ require "nowpayments/http"
7
+ require "nowpayments/ipn"
8
+ require "nowpayments/helpers"
9
+ require "nowpayments/client"
10
+
11
+ module NowPayments
12
+ # Convenience aliases (Node.js style)
13
+ def self.verify_ipn_signature(payload, signature, ipn_secret)
14
+ IPN.verify_signature(payload, signature, ipn_secret)
15
+ end
16
+
17
+ def self.create_ipn_signature(payload, ipn_secret)
18
+ IPN.create_signature(payload, ipn_secret)
19
+ end
20
+ end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nowpayments-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foisalislambd
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-10-02 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: faraday
@@ -15,7 +16,7 @@ dependencies:
15
16
  requirements:
16
17
  - - ">="
17
18
  - !ruby/object:Gem::Version
18
- version: '1.0'
19
+ version: '2.12'
19
20
  - - "<"
20
21
  - !ruby/object:Gem::Version
21
22
  version: '3'
@@ -25,7 +26,7 @@ dependencies:
25
26
  requirements:
26
27
  - - ">="
27
28
  - !ruby/object:Gem::Version
28
- version: '1.0'
29
+ version: '2.12'
29
30
  - - "<"
30
31
  - !ruby/object:Gem::Version
31
32
  version: '3'
@@ -47,13 +48,14 @@ files:
47
48
  - lib/nowpayments/http.rb
48
49
  - lib/nowpayments/ipn.rb
49
50
  - lib/nowpayments/version.rb
50
- homepage: https://github.com/Foisalislambd/nowpayments-ruby
51
+ homepage: https://github.com/foisalislambd/nowpayments-package/tree/main/packages/ruby
51
52
  licenses:
52
53
  - MIT
53
54
  metadata:
54
- homepage_uri: https://github.com/Foisalislambd/nowpayments-ruby
55
- source_code_uri: https://github.com/Foisalislambd/nowpayments-ruby
56
- changelog_uri: https://github.com/Foisalislambd/nowpayments-ruby/blob/main/CHANGELOG.md
55
+ homepage_uri: https://github.com/foisalislambd/nowpayments-package/tree/main/packages/ruby
56
+ source_code_uri: https://github.com/foisalislambd/nowpayments-package
57
+ changelog_uri: https://github.com/foisalislambd/nowpayments-package/blob/main/CHANGELOG.md
58
+ post_install_message:
57
59
  rdoc_options: []
58
60
  require_paths:
59
61
  - lib
@@ -61,14 +63,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
63
  requirements:
62
64
  - - ">="
63
65
  - !ruby/object:Gem::Version
64
- version: 2.7.0
66
+ version: 3.1.0
65
67
  required_rubygems_version: !ruby/object:Gem::Requirement
66
68
  requirements:
67
69
  - - ">="
68
70
  - !ruby/object:Gem::Version
69
71
  version: '0'
70
72
  requirements: []
71
- rubygems_version: 3.6.9
73
+ rubygems_version: 3.5.22
74
+ signing_key:
72
75
  specification_version: 4
73
76
  summary: Full-featured Ruby SDK for NOWPayments cryptocurrency payment API
74
77
  test_files: []