listo-delivery 0.1.1

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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +152 -0
  4. data/lib/listo_delivery/client.rb +170 -0
  5. data/lib/listo_delivery/errors.rb +53 -0
  6. data/lib/listo_delivery/generated/api_version.rb +12 -0
  7. data/lib/listo_delivery/generated/client_operations.rb +82 -0
  8. data/lib/listo_delivery/generated/loader.rb +69 -0
  9. data/lib/listo_delivery/generated/models/balance.rb +48 -0
  10. data/lib/listo_delivery/generated/models/cancel_delivery_request.rb +29 -0
  11. data/lib/listo_delivery/generated/models/card_onboarding_link.rb +21 -0
  12. data/lib/listo_delivery/generated/models/card_status.rb +22 -0
  13. data/lib/listo_delivery/generated/models/connect_request.rb +28 -0
  14. data/lib/listo_delivery/generated/models/courier_ping.rb +58 -0
  15. data/lib/listo_delivery/generated/models/create_connect_request_request.rb +35 -0
  16. data/lib/listo_delivery/generated/models/create_connection_request.rb +21 -0
  17. data/lib/listo_delivery/generated/models/create_delivery_request.rb +189 -0
  18. data/lib/listo_delivery/generated/models/create_quote_request.rb +111 -0
  19. data/lib/listo_delivery/generated/models/create_store_owner_request.rb +26 -0
  20. data/lib/listo_delivery/generated/models/create_store_request.rb +104 -0
  21. data/lib/listo_delivery/generated/models/create_withdrawal_request.rb +29 -0
  22. data/lib/listo_delivery/generated/models/delivery.rb +879 -0
  23. data/lib/listo_delivery/generated/models/delivery_courier_location_event.rb +23 -0
  24. data/lib/listo_delivery/generated/models/delivery_list.rb +34 -0
  25. data/lib/listo_delivery/generated/models/delivery_message_skipped.rb +37 -0
  26. data/lib/listo_delivery/generated/models/delivery_message_skipped_event.rb +23 -0
  27. data/lib/listo_delivery/generated/models/delivery_status_changed_event.rb +23 -0
  28. data/lib/listo_delivery/generated/models/delivery_updated_event.rb +23 -0
  29. data/lib/listo_delivery/generated/models/error.rb +56 -0
  30. data/lib/listo_delivery/generated/models/error_response.rb +20 -0
  31. data/lib/listo_delivery/generated/models/event_list.rb +34 -0
  32. data/lib/listo_delivery/generated/models/ledger_entry.rb +62 -0
  33. data/lib/listo_delivery/generated/models/ledger_entry_list.rb +34 -0
  34. data/lib/listo_delivery/generated/models/manifest.rb +20 -0
  35. data/lib/listo_delivery/generated/models/manifest_item.rb +36 -0
  36. data/lib/listo_delivery/generated/models/owner_invitation.rb +27 -0
  37. data/lib/listo_delivery/generated/models/ping.rb +56 -0
  38. data/lib/listo_delivery/generated/models/quote.rb +99 -0
  39. data/lib/listo_delivery/generated/models/refund_delivery_request.rb +28 -0
  40. data/lib/listo_delivery/generated/models/request_log.rb +91 -0
  41. data/lib/listo_delivery/generated/models/request_log_detail.rb +105 -0
  42. data/lib/listo_delivery/generated/models/request_log_list.rb +34 -0
  43. data/lib/listo_delivery/generated/models/reschedule_delivery_request.rb +41 -0
  44. data/lib/listo_delivery/generated/models/same_day_terms.rb +106 -0
  45. data/lib/listo_delivery/generated/models/send_test_webhook_request.rb +22 -0
  46. data/lib/listo_delivery/generated/models/store.rb +235 -0
  47. data/lib/listo_delivery/generated/models/store_balance.rb +48 -0
  48. data/lib/listo_delivery/generated/models/store_connected_event.rb +23 -0
  49. data/lib/listo_delivery/generated/models/store_list.rb +34 -0
  50. data/lib/listo_delivery/generated/models/store_updated_event.rb +23 -0
  51. data/lib/listo_delivery/generated/models/update_store_request.rb +94 -0
  52. data/lib/listo_delivery/generated/models/webhook_delivery.rb +99 -0
  53. data/lib/listo_delivery/generated/models/webhook_delivery_list.rb +34 -0
  54. data/lib/listo_delivery/generated/models/webhook_event.rb +69 -0
  55. data/lib/listo_delivery/generated/models/webhook_event_envelope.rb +35 -0
  56. data/lib/listo_delivery/generated/models/withdrawal.rb +91 -0
  57. data/lib/listo_delivery/generated/models/withdrawal_list.rb +34 -0
  58. data/lib/listo_delivery/generated/models/withdrawal_updated_event.rb +23 -0
  59. data/lib/listo_delivery/generated/resources/balance.rb +43 -0
  60. data/lib/listo_delivery/generated/resources/card_onboarding.rb +70 -0
  61. data/lib/listo_delivery/generated/resources/connect_requests.rb +31 -0
  62. data/lib/listo_delivery/generated/resources/connections.rb +31 -0
  63. data/lib/listo_delivery/generated/resources/deliveries.rb +128 -0
  64. data/lib/listo_delivery/generated/resources/events.rb +59 -0
  65. data/lib/listo_delivery/generated/resources/ledger.rb +47 -0
  66. data/lib/listo_delivery/generated/resources/quotes.rb +31 -0
  67. data/lib/listo_delivery/generated/resources/request_logs.rb +45 -0
  68. data/lib/listo_delivery/generated/resources/stores.rb +143 -0
  69. data/lib/listo_delivery/generated/resources/webhooks.rb +46 -0
  70. data/lib/listo_delivery/generated/resources/withdrawals.rb +60 -0
  71. data/lib/listo_delivery/model.rb +69 -0
  72. data/lib/listo_delivery/util.rb +76 -0
  73. data/lib/listo_delivery/version.rb +7 -0
  74. data/lib/listo_delivery/webhooks.rb +98 -0
  75. data/lib/listo_delivery.rb +16 -0
  76. data/listo_delivery.gemspec +27 -0
  77. metadata +120 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "uri"
5
+
6
+ module ListoDelivery
7
+ # Argument handling shared by the generated resource methods.
8
+ # @api private
9
+ module Util
10
+ module_function
11
+
12
+ # A path parameter, escaped so an id can never change the route.
13
+ def segment(value)
14
+ text = value.to_s
15
+ raise ArgumentError, "a path parameter must not be blank" if text.strip.empty?
16
+
17
+ ERB::Util.url_encode(text)
18
+ end
19
+
20
+ # The request body: the positional Hash merged with keyword fields, nil
21
+ # when neither was given (so nothing is sent) unless `required` names
22
+ # the operation that needs one.
23
+ def body(body, fields, required: nil)
24
+ reject_idempotency_key(fields)
25
+ merged = merge(body, fields)
26
+ raise ArgumentError, "#{required} needs a request body" if merged.nil? && required
27
+
28
+ merged
29
+ end
30
+
31
+ # The query string pairs as a Hash, nil when empty.
32
+ def query(query, fields)
33
+ reject_idempotency_key(fields)
34
+ merged = merge(query, fields)
35
+ merged&.reject { |_, v| v.nil? }
36
+ end
37
+
38
+ # Only the operations that declare the Idempotency-Key header take
39
+ # `idempotency_key:`, and those methods capture it as a keyword before
40
+ # `fields` exists. One that reaches `fields` belongs to an operation
41
+ # without the header, where it would otherwise be sent as a body or
42
+ # query field the API drops, and the caller would think the call was
43
+ # protected. A body field genuinely named so goes inside the Hash.
44
+ def reject_idempotency_key(fields)
45
+ return unless fields.key?(:idempotency_key)
46
+
47
+ raise ArgumentError,
48
+ "this operation does not take an idempotency key; " \
49
+ "to send a field named idempotency_key, put it inside the Hash argument"
50
+ end
51
+
52
+ def merge(first, fields)
53
+ base = first.nil? ? nil : plain(first)
54
+ raise ArgumentError, "expected a Hash, got #{first.class}" unless base.nil? || base.is_a?(Hash)
55
+
56
+ extra = plain(fields)
57
+ return base if extra.empty?
58
+
59
+ (base || {}).merge(extra)
60
+ end
61
+
62
+ # Models and symbol keys become plain JSON-shaped data.
63
+ def plain(value)
64
+ case value
65
+ when Model then plain(value.to_h)
66
+ when Hash then value.each_with_object({}) { |(k, v), h| h[k.to_s] = plain(v) }
67
+ when Array then value.map { |v| plain(v) }
68
+ else value
69
+ end
70
+ end
71
+
72
+ def encode_query(pairs)
73
+ URI.encode_www_form(pairs)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ListoDelivery
4
+ # The SDK's own version. The API release it was generated from is
5
+ # ListoDelivery::API_VERSION (generated/api_version.rb).
6
+ VERSION = "0.1.1"
7
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "openssl"
5
+
6
+ module ListoDelivery
7
+ # Checks that a webhook really came from Listo. Mirrors the server's own
8
+ # signing rule:
9
+ #
10
+ # X-Listo-Signature: t=<unix seconds>,v1=<hex hmac-sha256 over "<t>.<raw body>">
11
+ #
12
+ # Pass the RAW body exactly as received, never re-serialized JSON, and
13
+ # every secret that is currently active (two during a rotation).
14
+ #
15
+ # event = ListoDelivery::Webhooks.construct_event(request.body.read, request.headers["X-Listo-Signature"], secret)
16
+ module Webhooks
17
+ HEADER = "X-Listo-Signature"
18
+ DEFAULT_TOLERANCE = 300
19
+ PATTERN = /\At=(\d+),v1=(\h+)\z/
20
+
21
+ # True only when the header is a well formed signature over the body,
22
+ # made within `tolerance` seconds of `now` (either direction), under any
23
+ # of the secrets. Never raises: anything malformed is false.
24
+ #
25
+ # @param raw_body [String]
26
+ # @param signature_header [String, nil]
27
+ # @param secrets [String, Array<String>]
28
+ # @param tolerance [Integer] seconds
29
+ # @param now [Integer, Time, nil] unix seconds; defaults to the current time
30
+ def self.verify(raw_body, signature_header, secrets, tolerance: DEFAULT_TOLERANCE, now: nil)
31
+ usable = Array(secrets).map(&:to_s).reject { |s| s.strip.empty? }
32
+ return false if usable.empty?
33
+
34
+ match = PATTERN.match(signature_header.to_s)
35
+ return false if match.nil?
36
+
37
+ timestamp = match[1].to_i
38
+ return false if (now_seconds(now) - timestamp).abs > tolerance.to_i
39
+
40
+ given = match[2]
41
+ usable.any? { |secret| secure_equal?(digest(timestamp, raw_body, secret), given) }
42
+ rescue ArgumentError, TypeError, EncodingError
43
+ false
44
+ end
45
+
46
+ # Verifies, then parses the body into the event class its `type` names
47
+ # (a type this SDK does not know comes back as a plain
48
+ # Models::WebhookEvent).
49
+ #
50
+ # @raise [ListoDelivery::SignatureError] when the signature does not verify
51
+ # @return [Models::WebhookEvent]
52
+ def self.construct_event(raw_body, signature_header, secrets, tolerance: DEFAULT_TOLERANCE, now: nil)
53
+ raise SignatureError unless verify(raw_body, signature_header, secrets, tolerance: tolerance, now: now)
54
+
55
+ parsed = JSON.parse(raw_body.to_s)
56
+ unless parsed.is_a?(Hash)
57
+ raise Error.new("The webhook body is not a JSON object.", code: "invalid_payload", retryable: false)
58
+ end
59
+
60
+ Models::WebhookEvent.build(parsed)
61
+ rescue JSON::ParserError
62
+ raise Error.new("The webhook body is not valid JSON.", code: "invalid_payload", retryable: false)
63
+ end
64
+
65
+ # The signed string is "<t>.<raw body>" over the bytes as received.
66
+ def self.digest(timestamp, raw_body, secret)
67
+ OpenSSL::HMAC.hexdigest("SHA256", secret.to_s.b, "#{timestamp}.".b + raw_body.to_s.b)
68
+ end
69
+
70
+ # The length check comes first because the constant-time comparison
71
+ # raises on unequal lengths, and a truncated signature is just invalid.
72
+ def self.secure_equal?(expected, given)
73
+ return false unless expected.bytesize == given.bytesize
74
+
75
+ OpenSSL.fixed_length_secure_compare(expected, given)
76
+ end
77
+
78
+ def self.now_seconds(now)
79
+ (now || Time.now).to_i
80
+ end
81
+
82
+ private_class_method :digest, :secure_equal?, :now_seconds
83
+ end
84
+
85
+ module Resources
86
+ # The webhook operations (generated) plus the signature helpers, so
87
+ # `client.webhooks.verify(...)` reads like the rest of the client.
88
+ class Webhooks
89
+ def verify(...)
90
+ ListoDelivery::Webhooks.verify(...)
91
+ end
92
+
93
+ def construct_event(...)
94
+ ListoDelivery::Webhooks.construct_event(...)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "listo_delivery/version"
4
+ require_relative "listo_delivery/errors"
5
+ require_relative "listo_delivery/util"
6
+ require_relative "listo_delivery/model"
7
+ require_relative "listo_delivery/generated/loader"
8
+ require_relative "listo_delivery/webhooks"
9
+ require_relative "listo_delivery/client"
10
+
11
+ # Official Ruby client for the Listo Delivery API.
12
+ #
13
+ # require "listo_delivery"
14
+ # client = ListoDelivery::Client.new(api_key: "lst_test_...")
15
+ module ListoDelivery
16
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/listo_delivery/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "listo-delivery"
7
+ spec.version = ListoDelivery::VERSION
8
+ spec.summary = "Official Ruby client for the Listo Delivery API"
9
+ spec.description = "Quote and book deliveries, manage stores and money, and verify webhooks with the Listo Delivery API. Standard library only."
10
+ spec.authors = ["Listo"]
11
+ spec.homepage = "https://docs.listo.delivery"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 3.1"
14
+ spec.metadata = {
15
+ "homepage_uri" => "https://docs.listo.delivery",
16
+ "documentation_uri" => "https://docs.listo.delivery/en/quickstart",
17
+ "changelog_uri" => "https://docs.listo.delivery/en/changelog",
18
+ "source_code_uri" => "https://github.com/listo-delivery/listo-ruby",
19
+ "bug_tracker_uri" => "https://github.com/listo-delivery/listo-ruby/issues",
20
+ "rubygems_mfa_required" => "true"
21
+ }
22
+
23
+ # Listed from the directory, never from git, so the gem builds the same
24
+ # from a tarball or a worktree.
25
+ spec.files = Dir.chdir(__dir__) { Dir["lib/**/*.rb"] + ["README.md", "LICENSE", "listo_delivery.gemspec"] }.sort
26
+ spec.require_paths = ["lib"]
27
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: listo-delivery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Listo
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Quote and book deliveries, manage stores and money, and verify webhooks
13
+ with the Listo Delivery API. Standard library only.
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - LICENSE
19
+ - README.md
20
+ - lib/listo_delivery.rb
21
+ - lib/listo_delivery/client.rb
22
+ - lib/listo_delivery/errors.rb
23
+ - lib/listo_delivery/generated/api_version.rb
24
+ - lib/listo_delivery/generated/client_operations.rb
25
+ - lib/listo_delivery/generated/loader.rb
26
+ - lib/listo_delivery/generated/models/balance.rb
27
+ - lib/listo_delivery/generated/models/cancel_delivery_request.rb
28
+ - lib/listo_delivery/generated/models/card_onboarding_link.rb
29
+ - lib/listo_delivery/generated/models/card_status.rb
30
+ - lib/listo_delivery/generated/models/connect_request.rb
31
+ - lib/listo_delivery/generated/models/courier_ping.rb
32
+ - lib/listo_delivery/generated/models/create_connect_request_request.rb
33
+ - lib/listo_delivery/generated/models/create_connection_request.rb
34
+ - lib/listo_delivery/generated/models/create_delivery_request.rb
35
+ - lib/listo_delivery/generated/models/create_quote_request.rb
36
+ - lib/listo_delivery/generated/models/create_store_owner_request.rb
37
+ - lib/listo_delivery/generated/models/create_store_request.rb
38
+ - lib/listo_delivery/generated/models/create_withdrawal_request.rb
39
+ - lib/listo_delivery/generated/models/delivery.rb
40
+ - lib/listo_delivery/generated/models/delivery_courier_location_event.rb
41
+ - lib/listo_delivery/generated/models/delivery_list.rb
42
+ - lib/listo_delivery/generated/models/delivery_message_skipped.rb
43
+ - lib/listo_delivery/generated/models/delivery_message_skipped_event.rb
44
+ - lib/listo_delivery/generated/models/delivery_status_changed_event.rb
45
+ - lib/listo_delivery/generated/models/delivery_updated_event.rb
46
+ - lib/listo_delivery/generated/models/error.rb
47
+ - lib/listo_delivery/generated/models/error_response.rb
48
+ - lib/listo_delivery/generated/models/event_list.rb
49
+ - lib/listo_delivery/generated/models/ledger_entry.rb
50
+ - lib/listo_delivery/generated/models/ledger_entry_list.rb
51
+ - lib/listo_delivery/generated/models/manifest.rb
52
+ - lib/listo_delivery/generated/models/manifest_item.rb
53
+ - lib/listo_delivery/generated/models/owner_invitation.rb
54
+ - lib/listo_delivery/generated/models/ping.rb
55
+ - lib/listo_delivery/generated/models/quote.rb
56
+ - lib/listo_delivery/generated/models/refund_delivery_request.rb
57
+ - lib/listo_delivery/generated/models/request_log.rb
58
+ - lib/listo_delivery/generated/models/request_log_detail.rb
59
+ - lib/listo_delivery/generated/models/request_log_list.rb
60
+ - lib/listo_delivery/generated/models/reschedule_delivery_request.rb
61
+ - lib/listo_delivery/generated/models/same_day_terms.rb
62
+ - lib/listo_delivery/generated/models/send_test_webhook_request.rb
63
+ - lib/listo_delivery/generated/models/store.rb
64
+ - lib/listo_delivery/generated/models/store_balance.rb
65
+ - lib/listo_delivery/generated/models/store_connected_event.rb
66
+ - lib/listo_delivery/generated/models/store_list.rb
67
+ - lib/listo_delivery/generated/models/store_updated_event.rb
68
+ - lib/listo_delivery/generated/models/update_store_request.rb
69
+ - lib/listo_delivery/generated/models/webhook_delivery.rb
70
+ - lib/listo_delivery/generated/models/webhook_delivery_list.rb
71
+ - lib/listo_delivery/generated/models/webhook_event.rb
72
+ - lib/listo_delivery/generated/models/webhook_event_envelope.rb
73
+ - lib/listo_delivery/generated/models/withdrawal.rb
74
+ - lib/listo_delivery/generated/models/withdrawal_list.rb
75
+ - lib/listo_delivery/generated/models/withdrawal_updated_event.rb
76
+ - lib/listo_delivery/generated/resources/balance.rb
77
+ - lib/listo_delivery/generated/resources/card_onboarding.rb
78
+ - lib/listo_delivery/generated/resources/connect_requests.rb
79
+ - lib/listo_delivery/generated/resources/connections.rb
80
+ - lib/listo_delivery/generated/resources/deliveries.rb
81
+ - lib/listo_delivery/generated/resources/events.rb
82
+ - lib/listo_delivery/generated/resources/ledger.rb
83
+ - lib/listo_delivery/generated/resources/quotes.rb
84
+ - lib/listo_delivery/generated/resources/request_logs.rb
85
+ - lib/listo_delivery/generated/resources/stores.rb
86
+ - lib/listo_delivery/generated/resources/webhooks.rb
87
+ - lib/listo_delivery/generated/resources/withdrawals.rb
88
+ - lib/listo_delivery/model.rb
89
+ - lib/listo_delivery/util.rb
90
+ - lib/listo_delivery/version.rb
91
+ - lib/listo_delivery/webhooks.rb
92
+ - listo_delivery.gemspec
93
+ homepage: https://docs.listo.delivery
94
+ licenses:
95
+ - MIT
96
+ metadata:
97
+ homepage_uri: https://docs.listo.delivery
98
+ documentation_uri: https://docs.listo.delivery/en/quickstart
99
+ changelog_uri: https://docs.listo.delivery/en/changelog
100
+ source_code_uri: https://github.com/listo-delivery/listo-ruby
101
+ bug_tracker_uri: https://github.com/listo-delivery/listo-ruby/issues
102
+ rubygems_mfa_required: 'true'
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '3.1'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.6.9
118
+ specification_version: 4
119
+ summary: Official Ruby client for the Listo Delivery API
120
+ test_files: []