eml 1.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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +10 -0
  3. data/.env.example +6 -0
  4. data/.gitignore +5 -0
  5. data/.rspec +5 -0
  6. data/.rubocop.yml +90 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE +21 -0
  9. data/README.md +87 -0
  10. data/eml.gemspec +37 -0
  11. data/lib/eml.rb +22 -0
  12. data/lib/eml/config.rb +49 -0
  13. data/lib/eml/data/countries.rb +260 -0
  14. data/lib/eml/data/currencies.rb +176 -0
  15. data/lib/eml/data/states.rb +4055 -0
  16. data/lib/eml/environment.rb +54 -0
  17. data/lib/eml/error.rb +25 -0
  18. data/lib/eml/error/rest.rb +42 -0
  19. data/lib/eml/error/rest/authentication.rb +21 -0
  20. data/lib/eml/error/rest/bad_request.rb +20 -0
  21. data/lib/eml/error/rest/daily_funding_limit.rb +19 -0
  22. data/lib/eml/error/rest/forbidden.rb +20 -0
  23. data/lib/eml/error/rest/internal_server.rb +9 -0
  24. data/lib/eml/error/rest/not_found.rb +20 -0
  25. data/lib/eml/error/rest/unprocessable_entity.rb +20 -0
  26. data/lib/eml/lib/endpoint_class.rb +42 -0
  27. data/lib/eml/parameters.rb +153 -0
  28. data/lib/eml/payload.rb +137 -0
  29. data/lib/eml/response.rb +103 -0
  30. data/lib/eml/uk.rb +51 -0
  31. data/lib/eml/uk/api_resource.rb +94 -0
  32. data/lib/eml/uk/config.rb +52 -0
  33. data/lib/eml/uk/lib/endpoint_class.rb +19 -0
  34. data/lib/eml/uk/lib/parse_date.rb +35 -0
  35. data/lib/eml/uk/model/transaction.rb +56 -0
  36. data/lib/eml/uk/parameters.rb +99 -0
  37. data/lib/eml/uk/parameters/agreement/show.rb +22 -0
  38. data/lib/eml/uk/parameters/card/activation.rb +24 -0
  39. data/lib/eml/uk/parameters/card/lock.rb +26 -0
  40. data/lib/eml/uk/parameters/card/register.rb +24 -0
  41. data/lib/eml/uk/parameters/card/reload.rb +24 -0
  42. data/lib/eml/uk/parameters/card/show.rb +102 -0
  43. data/lib/eml/uk/parameters/card/transaction.rb +59 -0
  44. data/lib/eml/uk/parameters/card/unload.rb +26 -0
  45. data/lib/eml/uk/parameters/card/unlock.rb +26 -0
  46. data/lib/eml/uk/parameters/card/void.rb +26 -0
  47. data/lib/eml/uk/payload.rb +94 -0
  48. data/lib/eml/uk/payload/agreement/show.rb +46 -0
  49. data/lib/eml/uk/payload/card/activation.rb +73 -0
  50. data/lib/eml/uk/payload/card/lock.rb +34 -0
  51. data/lib/eml/uk/payload/card/register.rb +70 -0
  52. data/lib/eml/uk/payload/card/reload.rb +38 -0
  53. data/lib/eml/uk/payload/card/show.rb +12 -0
  54. data/lib/eml/uk/payload/card/transaction.rb +12 -0
  55. data/lib/eml/uk/payload/card/unload.rb +38 -0
  56. data/lib/eml/uk/payload/card/unlock.rb +23 -0
  57. data/lib/eml/uk/payload/card/void.rb +23 -0
  58. data/lib/eml/uk/payload/contactentity.rb +64 -0
  59. data/lib/eml/uk/payload/iso.rb +48 -0
  60. data/lib/eml/uk/payload/location.rb +30 -0
  61. data/lib/eml/uk/resources/agreement.rb +32 -0
  62. data/lib/eml/uk/resources/card.rb +181 -0
  63. data/lib/eml/uk/response.rb +29 -0
  64. data/lib/eml/uk/responses/agreement/show.rb +16 -0
  65. data/lib/eml/uk/responses/card/reload.rb +19 -0
  66. data/lib/eml/uk/responses/card/show.rb +53 -0
  67. data/lib/eml/uk/responses/card/transaction.rb +24 -0
  68. data/lib/eml/version.rb +6 -0
  69. data/sorbet/config +2 -0
  70. data/sorbet/rbi/gems/addressable.rbi +198 -0
  71. data/sorbet/rbi/gems/ast.rbi +47 -0
  72. data/sorbet/rbi/gems/concurrent-ruby.rbi +218 -0
  73. data/sorbet/rbi/gems/crack.rbi +47 -0
  74. data/sorbet/rbi/gems/docile.rbi +31 -0
  75. data/sorbet/rbi/gems/domain_name.rbi +51 -0
  76. data/sorbet/rbi/gems/dotenv.rbi +67 -0
  77. data/sorbet/rbi/gems/faker.rbi +1350 -0
  78. data/sorbet/rbi/gems/hashdiff.rbi +65 -0
  79. data/sorbet/rbi/gems/http-cookie.rbi +92 -0
  80. data/sorbet/rbi/gems/http-form_data.rbi +73 -0
  81. data/sorbet/rbi/gems/http.rbi +609 -0
  82. data/sorbet/rbi/gems/http_parser.rb.rbi +36 -0
  83. data/sorbet/rbi/gems/i18n.rbi +191 -0
  84. data/sorbet/rbi/gems/jaro_winkler.rbi +14 -0
  85. data/sorbet/rbi/gems/parallel.rbi +81 -0
  86. data/sorbet/rbi/gems/parser.rbi +835 -0
  87. data/sorbet/rbi/gems/public_suffix.rbi +103 -0
  88. data/sorbet/rbi/gems/rainbow.rbi +117 -0
  89. data/sorbet/rbi/gems/rspec-core.rbi +1655 -0
  90. data/sorbet/rbi/gems/rspec-support.rbi +126 -0
  91. data/sorbet/rbi/gems/rspec.rbi +14 -0
  92. data/sorbet/rbi/gems/rubocop-performance.rbi +276 -0
  93. data/sorbet/rbi/gems/rubocop-rspec.rbi +860 -0
  94. data/sorbet/rbi/gems/rubocop.rbi +6943 -0
  95. data/sorbet/rbi/gems/ruby-progressbar.rbi +304 -0
  96. data/sorbet/rbi/gems/simplecov-html.rbi +30 -0
  97. data/sorbet/rbi/gems/simplecov.rbi +225 -0
  98. data/sorbet/rbi/gems/unf.rbi +18 -0
  99. data/sorbet/rbi/gems/unicode-display_width.rbi +16 -0
  100. data/sorbet/rbi/gems/vcr.rbi +503 -0
  101. data/sorbet/rbi/gems/webmock.rbi +521 -0
  102. data/sorbet/rbi/hidden-definitions/errors.txt +4802 -0
  103. data/sorbet/rbi/hidden-definitions/hidden.rbi +10700 -0
  104. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8682 -0
  105. data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
  106. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  107. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  108. data/sorbet/rbi/todo.rbi +24 -0
  109. data/spec/config_spec.rb +26 -0
  110. data/spec/helpers/config_helper.rb +21 -0
  111. data/spec/helpers/vcr_helper.rb +19 -0
  112. data/spec/spec_helper.rb +120 -0
  113. data/spec/uk/api_resource_spec.rb +39 -0
  114. data/spec/uk/resources/agreement_spec.rb +23 -0
  115. data/spec/uk/resources/card_spec.rb +197 -0
  116. metadata +339 -0
@@ -0,0 +1,137 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # A payload is the data sent in the body of a request such as POST or PUT
5
+ module EML
6
+ class Payload
7
+ extend T::Sig
8
+
9
+ sig { params(payload: T::Hash[Symbol, T.untyped]).void }
10
+ def initialize(payload)
11
+ assign_params(payload)
12
+ validate_required_payload_presence
13
+ end
14
+
15
+ ENDPOINT_CLASS_TYPE = "Payload"
16
+
17
+ class << self
18
+ extend T::Sig
19
+
20
+ sig do
21
+ params(
22
+ resource_class: T.untyped,
23
+ endpoint: String,
24
+ payload: T::Hash[Symbol, T.untyped]
25
+ ).returns(T.untyped)
26
+ end
27
+ def convert(resource_class, endpoint, payload)
28
+ endpoint_class = EML::EndpointClass.(
29
+ class_type: ENDPOINT_CLASS_TYPE,
30
+ resource_class: resource_class,
31
+ endpoint: endpoint
32
+ )
33
+
34
+ convert_with_endpoint_class(
35
+ endpoint: endpoint, endpoint_class: endpoint_class,
36
+ payload: payload, resource_class: resource_class
37
+ )
38
+ end
39
+
40
+ protected
41
+
42
+ sig do
43
+ params(
44
+ endpoint_class: T.nilable(T.class_of(EML::Payload)),
45
+ resource_class: T.untyped,
46
+ endpoint: String,
47
+ payload: T::Hash[Symbol, T.untyped]
48
+ ).returns(T.untyped)
49
+ end
50
+ def convert_with_endpoint_class(
51
+ endpoint_class:, resource_class:, endpoint:, payload:
52
+ )
53
+ return endpoint_class.new(payload) unless endpoint_class.nil?
54
+ return payload if payload.empty?
55
+
56
+ message = "No endpoint class can be found for resource, " \
57
+ "#{resource_class}, with endpoint #{endpoint}"
58
+ raise ArgumentError, message
59
+ end
60
+ end
61
+
62
+ sig { returns(T::Hash[Symbol, T.untyped]) }
63
+ def to_h
64
+ instance_variables.each_with_object({}) do |variable_name, params|
65
+ key = variable_name.to_s[1..-1].to_sym
66
+ params[key] = instance_variable_get(variable_name)
67
+ params[key] = params[key].to_h if params[key].respond_to?(:to_h)
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ sig do
74
+ params(param_name: Symbol, param_value: String, values: T::Array[String]).
75
+ void
76
+ end
77
+ def validate_enum(param_name, param_value, values)
78
+ return if values.include?(param_value)
79
+
80
+ value_message = values.dup.
81
+ tap { |vals| vals[-1] = "or #{vals.last}" }.
82
+ join(", ")
83
+ raise(ArgumentError, "#{param_name} must be one of #{value_message}")
84
+ end
85
+
86
+ sig do
87
+ params(param_name: Symbol, param_value: String, length: Integer).void
88
+ end
89
+ def validate_max_length(param_name, param_value, length)
90
+ return if param_value.to_s.length <= length
91
+
92
+ raise(
93
+ ArgumentError,
94
+ "#{param_name} is expected to be no more than #{length} characters"
95
+ )
96
+ end
97
+
98
+ private
99
+
100
+ sig { params(params: T::Hash[Symbol, T.untyped]).void }
101
+ def assign_params(params)
102
+ params.each do |name, value|
103
+ __send__(:"#{name}=", value)
104
+ end
105
+ end
106
+
107
+ sig { void }
108
+ def validate_required_payload_presence
109
+ return unless self.class.const_defined?(:REQUIRED_VALUES)
110
+
111
+ required_params = self.class.const_get(:REQUIRED_VALUES)
112
+ required_params.each do |param_name|
113
+ validate_required_payload_value_presence(param_name)
114
+ end
115
+ end
116
+
117
+ # rubocop:disable Metrics/MethodLength
118
+ sig { params(value_key: Symbol, object: EML::Payload).void }
119
+ def validate_required_payload_value_presence(value_key, object: self)
120
+ names = value_key.to_s.split(".")
121
+ value = object.instance_variable_get(:"@#{names.shift}")
122
+
123
+ if value.nil?
124
+ raise(
125
+ ArgumentError,
126
+ "#{value_key} was not supplied but it is a required field"
127
+ )
128
+ end
129
+
130
+ if names.length.positive?
131
+ value_key = names.join(".").to_sym
132
+ validate_required_payload_value_presence(value_key, object: value)
133
+ end
134
+ end
135
+ # rubocop:enable Metrics/MethodLength
136
+ end
137
+ end
@@ -0,0 +1,103 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ class Response
6
+ extend T::Sig
7
+
8
+ sig { params(field_name: Symbol).void }
9
+ def self.field(field_name)
10
+ define_method(field_name) do
11
+ string_name = field_name.to_s
12
+ body[string_name]
13
+ end
14
+ end
15
+
16
+ sig { params(response: HTTP::Response, id: T.nilable(String)).void }
17
+ def initialize(response, id: nil)
18
+ @response = T.let(response, HTTP::Response)
19
+ @body = T.let(nil, T.nilable(String))
20
+ @http_status = T.let(nil, T.nilable(Integer))
21
+ @url = T.let(nil, T.nilable(String))
22
+ raise_error(id: id) unless http_status == 200
23
+ end
24
+
25
+ # TODO: sometimes we get a string and sometimes a hash. When it's a hash,
26
+ # there should be a custom response object
27
+ sig { returns(T.untyped) }
28
+ def body
29
+ @body ||= JSON.parse(@response.body.to_s)
30
+ rescue JSON::ParserError
31
+ @body = ""
32
+ end
33
+
34
+ sig { returns(T.nilable(String)) }
35
+ def error
36
+ body.fetch("message", nil) if body.respond_to?(:fetch)
37
+ end
38
+
39
+ sig { returns(T::Hash[Symbol, String]) }
40
+ def headers
41
+ @response.headers
42
+ end
43
+
44
+ sig { returns(Integer) }
45
+ def http_status
46
+ @http_status ||= @response.status.code
47
+ end
48
+
49
+ sig { returns(T::Boolean) }
50
+ def success?
51
+ http_status == 200
52
+ end
53
+
54
+ sig { returns(String) }
55
+ def url
56
+ @url ||= T.unsafe(@response).url.to_s
57
+ end
58
+
59
+ private
60
+
61
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
62
+ sig { params(id: T.nilable(String)).returns(EML::RESTError) }
63
+ def raise_error(id: nil)
64
+ check_for_incorrectly_categorised_errors
65
+
66
+ case http_status
67
+ when 400
68
+ raise T.unsafe(EML::REST::BadRequestError).new(error, self)
69
+ when 401
70
+ raise T.unsafe(EML::REST::AuthenticationError).new(nil, self)
71
+ when 403
72
+ raise T.unsafe(EML::REST::ForbiddenError).new(nil, self)
73
+ when 404
74
+ raise T.unsafe(EML::REST::NotFoundError).new(nil, self, id: T.must(id))
75
+ when 500
76
+ process_internal_server_error
77
+ raise T.unsafe(EML::REST::InternalServerError).new(error, self)
78
+ end
79
+
80
+ raise T.unsafe(EML::RESTError).
81
+ new("Unknown error (#{http_status}): #{error}", self)
82
+ end
83
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
84
+
85
+ sig { returns(T.nilable(EML::RESTError)) }
86
+ def check_for_incorrectly_categorised_errors
87
+ if error.match?(/is not in (the proper|a valid) status/)
88
+ raise T.unsafe(EML::REST::UnprocessableEntityError).new(error, self)
89
+ end
90
+ end
91
+
92
+ sig { returns(T.nilable(EML::RESTError)) }
93
+ def process_internal_server_error
94
+ case error
95
+ when "Card reload could not be completed because it violated the " \
96
+ "funding limit: 'Daily Max Reload Amount Funding Limit'"
97
+ raise T.unsafe(EML::REST::DailyFundingLimitError).new(nil, self)
98
+ when "Invalid username or password."
99
+ raise T.unsafe(EML::REST::AuthenticationError).new(nil, self)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ module UK
6
+ end
7
+ end
8
+
9
+ require "eml/uk/api_resource"
10
+ require "eml/uk/config"
11
+
12
+ require "eml/uk/lib/endpoint_class"
13
+ require "eml/uk/lib/parse_date"
14
+
15
+ require "eml/uk/model/transaction"
16
+
17
+ require "eml/uk/parameters"
18
+ require "eml/uk/parameters/agreement/show"
19
+ require "eml/uk/parameters/card/activation"
20
+ require "eml/uk/parameters/card/show"
21
+ require "eml/uk/parameters/card/lock"
22
+ require "eml/uk/parameters/card/register"
23
+ require "eml/uk/parameters/card/reload"
24
+ require "eml/uk/parameters/card/transaction"
25
+ require "eml/uk/parameters/card/unload"
26
+ require "eml/uk/parameters/card/unlock"
27
+
28
+ require "eml/uk/payload"
29
+ require "eml/uk/payload/agreement/show"
30
+ require "eml/uk/payload/iso"
31
+ require "eml/uk/payload/contactentity"
32
+ require "eml/uk/payload/location"
33
+ require "eml/uk/payload/nil"
34
+ require "eml/uk/payload/card/activation"
35
+ require "eml/uk/payload/card/show"
36
+ require "eml/uk/payload/card/lock"
37
+ require "eml/uk/payload/card/register"
38
+ require "eml/uk/payload/card/reload"
39
+ require "eml/uk/payload/card/transaction"
40
+ require "eml/uk/payload/card/unload"
41
+ require "eml/uk/payload/card/unlock"
42
+ require "eml/uk/payload/card/void"
43
+
44
+ require "eml/uk/resources/agreement"
45
+ require "eml/uk/resources/card"
46
+
47
+ require "eml/uk/response"
48
+ require "eml/uk/responses/agreement/show"
49
+ require "eml/uk/responses/card/reload"
50
+ require "eml/uk/responses/card/show"
51
+ require "eml/uk/responses/card/transaction"
@@ -0,0 +1,94 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "cgi"
5
+
6
+ module EML
7
+ module UK
8
+ class APIResource
9
+ extend T::Sig
10
+
11
+ LANGUAGE = "en"
12
+ private_constant :LANGUAGE
13
+
14
+ sig { params(id: T.nilable(String)).void }
15
+ def initialize(id: nil)
16
+ @id = T.let(id, T.nilable(String))
17
+ @headers = T.let(nil, T.nilable(T::Hash[String, String]))
18
+ end
19
+
20
+ sig do
21
+ params(
22
+ action: Symbol,
23
+ endpoint: String,
24
+ payload: T::Hash[Symbol, T.untyped],
25
+ params: T::Hash[Symbol, T.untyped]
26
+ ).returns(EML::Response)
27
+ end
28
+ def request(action, endpoint = "", payload: {}, params: {})
29
+ payload = EML::UK::Payload.convert(self.class, endpoint, payload)
30
+ params = EML::UK::Parameters.convert(self.class, endpoint, params)
31
+ url = resource_url(endpoint)
32
+
33
+ response = HTTP.headers(headers).
34
+ public_send(action, url, json: payload.to_h, params: params.to_h)
35
+ response_class(endpoint).new(response, id: @id)
36
+ end
37
+
38
+ protected
39
+
40
+ sig { params(endpoint: String).returns(String) }
41
+ def resource_url(endpoint)
42
+ endpoint_base = self.class.const_get(:ENDPOINT_BASE)
43
+
44
+ if @id.nil?
45
+ "#{domain}/#{endpoint_base}/#{endpoint}"
46
+ else
47
+ "#{domain}/#{endpoint_base}/#{@id}/#{endpoint}"
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ sig { returns(T::Hash[Symbol, T.nilable(String)]) }
54
+ def credentials
55
+ config = EML::UK.config
56
+ {
57
+ username: config.username,
58
+ password: config.password,
59
+ }
60
+ end
61
+
62
+ sig { returns(String) }
63
+ def domain
64
+ if EML::UK.config.environment.test?
65
+ "https://webservices.xtest.storefinancial.net/api/v1/#{LANGUAGE}"
66
+ else
67
+ "https://webservices.storefinancial.net/api/v1/#{LANGUAGE}"
68
+ end
69
+ end
70
+
71
+ sig { returns(T::Hash[String, String]) }
72
+ def headers
73
+ @headers ||= { "Authorization" => "Basic #{base64_credentials}" }
74
+ end
75
+
76
+ sig { returns(String) }
77
+ def base64_credentials
78
+ username = credentials[:username]
79
+ password = credentials[:password]
80
+ Base64.encode64("#{username}:#{password}").tr("\n", "")
81
+ end
82
+
83
+ sig { params(endpoint: String).returns(T.class_of(EML::Response)) }
84
+ def response_class(endpoint)
85
+ endpoint_class = EML::UK::EndpointClass.(
86
+ class_type: "Responses",
87
+ resource_class: self.class,
88
+ endpoint: endpoint
89
+ )
90
+ endpoint_class || EML::Response
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,52 @@
1
+ # typed: ignore
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ module UK
6
+ class << self
7
+ extend T::Sig
8
+
9
+ sig { void }
10
+ def configure
11
+ yield config
12
+ end
13
+
14
+ sig { returns(Config) }
15
+ def config
16
+ @config ||= T.let(Config.new, Config)
17
+ end
18
+ end
19
+
20
+ class Config < ::EML::Config
21
+ extend T::Sig
22
+
23
+ sig { returns(String) }
24
+ attr_accessor :username
25
+
26
+ sig { returns(String) }
27
+ attr_accessor :password
28
+
29
+ sig { returns(String) }
30
+ attr_accessor :merchant_group
31
+
32
+ sig { returns(String) }
33
+ attr_accessor :program
34
+
35
+ sig { returns(String) }
36
+ attr_accessor :search_parameter
37
+
38
+ private
39
+
40
+ sig { params(param: Symbol).void }
41
+ def require_error(param)
42
+ raise(
43
+ ArgumentError,
44
+ "#{param} is required but hasn't been set.\n" \
45
+ "EML::UK::Config.configuration do |config|\n" +
46
+ %( config.#{param} = "value") + "\n" \
47
+ "end"
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ module UK
6
+ class EndpointClass < ::EML::EndpointClass
7
+ extend T::Sig
8
+
9
+ private
10
+
11
+ sig { returns(String) }
12
+ def action_class_name
13
+ return "Show" if @endpoint.empty?
14
+
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end