bayarcash 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.
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ # FPX Direct Debit constants: application types, payer id types, frequency modes,
5
+ # and mandate status codes, together with their human-readable labels.
6
+ class FpxDirectDebit
7
+ # Application type
8
+ ENROLMENT = "01"
9
+ MAINTENANCE = "02"
10
+ TERMINATION = "03"
11
+
12
+ # Buyer / payer ID type
13
+ NRIC = 1
14
+ OLD_IC = 2
15
+ PASSPORT = 3
16
+ BUSINESS_REGISTRATION = 4
17
+ OTHERS = 5
18
+
19
+ # Frequency mode
20
+ MODE_DAILY = "DL"
21
+ MODE_WEEKLY = "WK"
22
+ MODE_MONTHLY = "MT"
23
+ MODE_YEARLY = "YR"
24
+
25
+ # Status code
26
+ STATUS_NEW = 0
27
+ STATUS_WAITING_APPROVAL = 1
28
+ STATUS_FAILED_BANK_VERIFICATION = 2
29
+ STATUS_ACTIVE = 3
30
+ STATUS_TERMINATED = 4
31
+ STATUS_APPROVED = 5
32
+ STATUS_REJECTED = 6
33
+ STATUS_CANCELLED = 7
34
+ STATUS_ERROR = 8
35
+
36
+ STATUS_LABELS = {
37
+ STATUS_NEW => "New",
38
+ STATUS_WAITING_APPROVAL => "Waiting Approval",
39
+ STATUS_FAILED_BANK_VERIFICATION => "Bank Verification Failed",
40
+ STATUS_APPROVED => "Approved",
41
+ STATUS_REJECTED => "Rejected",
42
+ STATUS_CANCELLED => "Cancelled",
43
+ STATUS_ERROR => "Error",
44
+ STATUS_ACTIVE => "Active",
45
+ STATUS_TERMINATED => "Terminated"
46
+ }.freeze
47
+
48
+ # @param status_code [Integer]
49
+ # @return [String] label for the status, or "UNKNOWN STATUS"
50
+ def self.get_status_text(status_code)
51
+ STATUS_LABELS.fetch(status_code.to_i, "UNKNOWN STATUS")
52
+ end
53
+
54
+ # @param application_type [String] one of {ENROLMENT}, {MAINTENANCE}, {TERMINATION}
55
+ # @return [String, nil]
56
+ def self.get_application_type_text(application_type)
57
+ case application_type
58
+ when ENROLMENT then "Enrollment"
59
+ when MAINTENANCE then "Maintenance"
60
+ when TERMINATION then "Termination"
61
+ end
62
+ end
63
+
64
+ # @param frequency_mode_code [String] one of {MODE_DAILY}, {MODE_WEEKLY}, {MODE_MONTHLY}, {MODE_YEARLY}
65
+ # @return [String, nil]
66
+ def self.get_frequency_mode_text(frequency_mode_code)
67
+ case frequency_mode_code
68
+ when MODE_DAILY then "Daily"
69
+ when MODE_WEEKLY then "Weekly"
70
+ when MODE_MONTHLY then "Monthly"
71
+ when MODE_YEARLY then "Yearly"
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ # FPX Direct Debit mandate operations (enrolment, maintenance, termination) and
5
+ # mandate/transaction retrieval.
6
+ module FpxDirectDebitPaymentIntent
7
+ # Enrol a new direct-debit mandate.
8
+ #
9
+ # @param data [Hash]
10
+ # @return [Bayarcash::Resources::FpxDirectDebitApplicationResource]
11
+ def create_fpx_direct_debit_enrollment(data)
12
+ Bayarcash::Resources::FpxDirectDebitApplicationResource.new(
13
+ post("mandates", data),
14
+ self
15
+ )
16
+ end
17
+
18
+ # Maintain (update) an existing mandate.
19
+ #
20
+ # @param mandate_id [String]
21
+ # @param data [Hash]
22
+ # @return [Bayarcash::Resources::FpxDirectDebitApplicationResource]
23
+ def create_fpx_direct_debit_maintenance(mandate_id, data)
24
+ Bayarcash::Resources::FpxDirectDebitApplicationResource.new(
25
+ put("mandates/#{mandate_id}", data),
26
+ self
27
+ )
28
+ end
29
+
30
+ # Terminate an existing mandate.
31
+ #
32
+ # @param mandate_id [String]
33
+ # @param data [Hash]
34
+ # @return [Bayarcash::Resources::FpxDirectDebitApplicationResource]
35
+ def create_fpx_direct_debit_termination(mandate_id, data)
36
+ Bayarcash::Resources::FpxDirectDebitApplicationResource.new(
37
+ delete("mandates/#{mandate_id}", data),
38
+ self
39
+ )
40
+ end
41
+
42
+ # Retrieve a direct-debit mandate transaction.
43
+ #
44
+ # @param id [String]
45
+ # @return [Bayarcash::Resources::TransactionResource]
46
+ def get_fpx_direct_debit_transaction(id)
47
+ Bayarcash::Resources::TransactionResource.new(
48
+ get("mandates/transactions/#{id}"),
49
+ self
50
+ )
51
+ end
52
+
53
+ # Deprecated misspelled alias, kept for backward compatibility.
54
+ #
55
+ # @deprecated Use {#get_fpx_direct_debit_transaction} instead.
56
+ def getfpx_direct_debitransaction(id)
57
+ get_fpx_direct_debit_transaction(id)
58
+ end
59
+
60
+ # Retrieve a direct-debit mandate.
61
+ #
62
+ # @param id [String]
63
+ # @return [Bayarcash::Resources::FpxDirectDebitResource]
64
+ def get_fpx_direct_debit(id)
65
+ Bayarcash::Resources::FpxDirectDebitResource.new(
66
+ get("mandates/#{id}")
67
+ )
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ # HTTP transport for the Bayarcash API.
5
+ #
6
+ # Requests default to form-urlencoded bodies (matching the PHP SDK's Guzzle
7
+ # `form_params` behaviour). To send a JSON body, pass a payload shaped like
8
+ # `{ json: {...} }`. Non-2xx responses are mapped to typed errors.
9
+ module MakesHttpRequests
10
+ # @return [Faraday::Connection]
11
+ attr_reader :connection
12
+
13
+ # @param uri [String]
14
+ # @return [Object] decoded response
15
+ def get(uri)
16
+ request(:get, uri)
17
+ end
18
+
19
+ # @param uri [String]
20
+ # @param payload [Hash]
21
+ # @return [Object] decoded response
22
+ def post(uri, payload = {})
23
+ request(:post, uri, payload)
24
+ end
25
+
26
+ # @param uri [String]
27
+ # @param payload [Hash]
28
+ # @return [Object] decoded response
29
+ def put(uri, payload = {})
30
+ request(:put, uri, payload)
31
+ end
32
+
33
+ # @param uri [String]
34
+ # @param payload [Hash]
35
+ # @return [Object] decoded response
36
+ def delete(uri, payload = {})
37
+ request(:delete, uri, payload)
38
+ end
39
+
40
+ # Retry a block until it returns a truthy value or the timeout elapses.
41
+ #
42
+ # @param timeout [Integer] seconds
43
+ # @param sleep_seconds [Integer]
44
+ # @yield the operation to retry
45
+ # @return [Object] the truthy result
46
+ # @raise [Bayarcash::TimeoutError] when the timeout elapses
47
+ def retry_until(timeout, sleep_seconds = 5)
48
+ start = Time.now.to_i
49
+
50
+ loop do
51
+ output = yield
52
+ return output if output
53
+
54
+ if Time.now.to_i - start < timeout
55
+ sleep(sleep_seconds)
56
+ else
57
+ output = [] if output.nil? || output == false
58
+ output = [output] unless output.is_a?(Array)
59
+ raise Bayarcash::TimeoutError.new(output)
60
+ end
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ # Build (or rebuild) the Faraday connection for the current base URI/token/timeout.
67
+ #
68
+ # @return [Faraday::Connection]
69
+ def build_connection
70
+ @connection = Faraday.new(url: base_uri) do |faraday|
71
+ faraday.headers["Authorization"] = "Bearer #{@token}"
72
+ faraday.headers["Accept"] = "application/json"
73
+ faraday.options.timeout = @timeout if @timeout
74
+ faraday.adapter Faraday.default_adapter
75
+ end
76
+ end
77
+
78
+ # Perform a request and decode the response, or map the error.
79
+ def request(verb, uri, payload = {})
80
+ payload ||= {}
81
+
82
+ response = connection.run_request(verb.to_s.downcase.to_sym, uri, nil, nil) do |req|
83
+ if json_payload?(payload)
84
+ req.headers["Content-Type"] = "application/json"
85
+ req.body = JSON.generate(payload[:json] || payload["json"])
86
+ elsif !payload.empty?
87
+ req.headers["Content-Type"] = "application/x-www-form-urlencoded"
88
+ req.body = Bayarcash::Util.build_query(payload)
89
+ end
90
+ end
91
+
92
+ status = response.status
93
+ return handle_request_error(response) if status < 200 || status > 299
94
+
95
+ parse_body(response.body.to_s)
96
+ end
97
+
98
+ # Map a non-2xx response to a typed error.
99
+ def handle_request_error(response)
100
+ body = response.body.to_s
101
+
102
+ case response.status
103
+ when 422
104
+ parsed = Bayarcash::Util.safe_json(body)
105
+ raise Bayarcash::ValidationError.new(parsed.is_a?(Hash) || parsed.is_a?(Array) ? parsed : {})
106
+ when 404
107
+ raise Bayarcash::NotFoundError.new
108
+ when 400
109
+ raise Bayarcash::FailedActionError, failed_action_message(body)
110
+ when 429
111
+ reset = response.headers["x-ratelimit-reset"]
112
+ raise Bayarcash::RateLimitExceededError.new(reset ? reset.to_i : nil)
113
+ else
114
+ raise Bayarcash::Error, body
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def json_payload?(payload)
121
+ payload.is_a?(Hash) && (payload.key?(:json) || payload.key?("json"))
122
+ end
123
+
124
+ def parse_body(body)
125
+ return body if body.nil? || body.empty?
126
+
127
+ parsed = Bayarcash::Util.safe_json(body)
128
+ return body if parsed.nil?
129
+ return body if Bayarcash::Util.php_falsy?(parsed)
130
+
131
+ parsed
132
+ end
133
+
134
+ def failed_action_message(body)
135
+ decoded = Bayarcash::Util.safe_json(body)
136
+ return body unless decoded.is_a?(Hash)
137
+
138
+ message = decoded["message"] || decoded["error"] || body
139
+ message = JSON.generate(message) if message.is_a?(Hash) || message.is_a?(Array)
140
+ message
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ # Manual (offline) bank transfer operations.
5
+ #
6
+ # These calls hit a different base URL than the versioned API (no /v2 or /v3
7
+ # segment) and support a multipart `proof_of_payment` file upload.
8
+ module ManualBankTransfer
9
+ REQUIRED_MANUAL_TRANSFER_FIELDS = %w[
10
+ portal_key buyer_name buyer_email order_amount order_no payment_gateway
11
+ merchant_bank_name merchant_bank_account merchant_bank_account_holder
12
+ bank_transfer_type bank_transfer_notes
13
+ ].freeze
14
+
15
+ FILE_CONTENT_TYPES = {
16
+ "jpg" => "image/jpeg",
17
+ "jpeg" => "image/jpeg",
18
+ "png" => "image/png",
19
+ "gif" => "image/gif",
20
+ "pdf" => "application/pdf"
21
+ }.freeze
22
+
23
+ # Create a manual bank transfer payment.
24
+ #
25
+ # @param data [Hash] payment and customer details (see README for the field list)
26
+ # @param allow_redirect [Boolean] whether to auto-follow HTTP redirects
27
+ # @return [Hash, String] response hash or raw string depending on the API response
28
+ # @raise [ArgumentError] when required fields are missing or invalid
29
+ # @raise [Bayarcash::Error] when the API request fails
30
+ def create_manual_bank_transfer(data, allow_redirect = false)
31
+ data = stringify_keys(data)
32
+
33
+ validate_manual_transfer_data(data)
34
+
35
+ data["bank_transfer_date"] ||= Date.today.strftime("%Y-%m-%d")
36
+
37
+ post_fields = prepare_manual_transfer_post_fields(data)
38
+
39
+ response = execute_manual_transfer_request(post_fields, allow_redirect)
40
+
41
+ process_manual_transfer_response(response[:body], response[:http_code], allow_redirect)
42
+ end
43
+
44
+ # Update the status of an existing manual bank transfer.
45
+ #
46
+ # @param ref_no [String] transaction reference number
47
+ # @param status [String] new status code
48
+ # @param amount [String] transaction amount
49
+ # @return [Object] decoded API response
50
+ # @raise [Bayarcash::Error] when the update fails
51
+ def update_manual_bank_transfer_status(ref_no, status, amount)
52
+ data = { "ref_no" => ref_no, "status" => status, "amount" => amount }
53
+
54
+ conn = Faraday.new do |faraday|
55
+ faraday.request :url_encoded
56
+ faraday.adapter Faraday.default_adapter
57
+ end
58
+
59
+ url = "#{manual_transfer_base_url}/manual-bank-transfer/update-status"
60
+
61
+ begin
62
+ response = conn.post(url) do |req|
63
+ req.headers["Accept"] = "application/json"
64
+ req.headers["Authorization"] = "Bearer #{@token}"
65
+ req.headers["Content-Type"] = "application/x-www-form-urlencoded"
66
+ req.body = data
67
+ end
68
+ rescue Faraday::Error => e
69
+ raise Bayarcash::Error, "Connection failed: #{e.message}"
70
+ end
71
+
72
+ handle_api_response(response.body.to_s, response.status)
73
+ end
74
+
75
+ # Extract structured data from an HTML form response.
76
+ #
77
+ # @param html_response [String]
78
+ # @return [Hash{String => String}]
79
+ def parse_manual_bank_transfer_response(html_response)
80
+ data = {}
81
+
82
+ if (form_id = html_response[/id="([^"]+)"/, 1])
83
+ data["form_id"] = form_id
84
+ end
85
+
86
+ if (return_url = html_response[/action="([^"]+)"/, 1])
87
+ data["return_url"] = return_url
88
+ end
89
+
90
+ html_response.scan(/<input name="([^"]+)" type="hidden" value="([^"]*)">/).each do |name, value|
91
+ data[name] = value
92
+ end
93
+
94
+ data
95
+ end
96
+
97
+ protected
98
+
99
+ # The manual-transfer base URL (note: no /v2 or /v3 segment).
100
+ #
101
+ # @return [String]
102
+ def manual_transfer_base_url
103
+ @sandbox ? "https://console.bayarcash-sandbox.com/api" : "https://console.bayar.cash/api"
104
+ end
105
+
106
+ private
107
+
108
+ def validate_manual_transfer_data(data)
109
+ REQUIRED_MANUAL_TRANSFER_FIELDS.each do |field|
110
+ if data[field].nil?
111
+ raise ArgumentError, "Required field '#{field}' is missing"
112
+ end
113
+ end
114
+
115
+ if data["payment_gateway"].nil? || data["payment_gateway"].to_s != "2"
116
+ raise ArgumentError, "Invalid payment gateway. Value must be 2 for manual bank transfers."
117
+ end
118
+
119
+ if data["proof_of_payment"] && !File.exist?(data["proof_of_payment"])
120
+ raise ArgumentError, "Proof of payment file does not exist"
121
+ end
122
+ end
123
+
124
+ def prepare_manual_transfer_post_fields(data)
125
+ post_fields = {}
126
+
127
+ data.each do |key, value|
128
+ post_fields[key] = value unless key == "proof_of_payment"
129
+ end
130
+
131
+ if data["proof_of_payment"] && File.exist?(data["proof_of_payment"])
132
+ path = data["proof_of_payment"]
133
+ post_fields["proof_of_payment"] = Faraday::Multipart::FilePart.new(
134
+ path,
135
+ file_content_type(path),
136
+ File.basename(path)
137
+ )
138
+ end
139
+
140
+ post_fields
141
+ end
142
+
143
+ def execute_manual_transfer_request(post_fields, allow_redirect)
144
+ conn = Faraday.new do |faraday|
145
+ faraday.request :multipart
146
+ faraday.request :url_encoded
147
+ faraday.adapter Faraday.default_adapter
148
+ end
149
+
150
+ url = "#{manual_transfer_base_url}/manual-bank-transfer"
151
+
152
+ begin
153
+ response = conn.post(url) do |req|
154
+ req.headers["Accept"] = "application/json"
155
+ req.headers["Authorization"] = "Bearer #{@token}"
156
+ req.body = post_fields
157
+ end
158
+
159
+ redirects = 0
160
+ while allow_redirect && response.status >= 300 && response.status < 400 &&
161
+ response.headers["location"] && redirects < 10
162
+ response = Faraday.get(response.headers["location"])
163
+ redirects += 1
164
+ end
165
+ rescue Faraday::Error => e
166
+ raise Bayarcash::Error, "cURL Error: #{e.message}"
167
+ end
168
+
169
+ { body: response.body.to_s, http_code: response.status }
170
+ end
171
+
172
+ def process_manual_transfer_response(response, http_code, allow_redirect)
173
+ if http_code >= 200 && http_code < 300
174
+ if response.include?("<form")
175
+ parsed = parse_manual_bank_transfer_response(response)
176
+
177
+ {
178
+ success: true,
179
+ html_form: response,
180
+ form_data: parsed,
181
+ return_url: parsed["return_url"]
182
+ }
183
+ else
184
+ decoded = Bayarcash::Util.safe_json(response)
185
+ decoded && !Bayarcash::Util.php_falsy?(decoded) ? decoded : response
186
+ end
187
+ elsif http_code >= 300 && http_code < 400 && !allow_redirect
188
+ { redirect_url: response }
189
+ else
190
+ handle_api_error(response, http_code)
191
+ end
192
+ end
193
+
194
+ def handle_api_error(response, http_code)
195
+ decoded = Bayarcash::Util.safe_json(response)
196
+
197
+ if decoded.is_a?(Hash) && decoded.key?("message")
198
+ raise Bayarcash::Error, decoded["message"]
199
+ else
200
+ raise Bayarcash::Error, "API Error (HTTP #{http_code}): #{response[0, 200]}"
201
+ end
202
+ end
203
+
204
+ def handle_api_response(response, http_code)
205
+ if http_code >= 200 && http_code < 300
206
+ decoded = Bayarcash::Util.safe_json(response)
207
+ decoded && !Bayarcash::Util.php_falsy?(decoded) ? decoded : response
208
+ else
209
+ handle_api_error(response, http_code)
210
+ end
211
+ end
212
+
213
+ def file_content_type(file_path)
214
+ extension = File.extname(file_path).delete(".").downcase
215
+ FILE_CONTENT_TYPES.fetch(extension, "application/octet-stream")
216
+ end
217
+
218
+ def stringify_keys(data)
219
+ (data || {}).each_with_object({}) do |(key, value), memo|
220
+ memo[key.to_s] = value
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Returned by {Bayarcash::Client#fpx_banks_list}.
6
+ class FpxBankResource < Resource
7
+ attributes :bank_name, :bank_display_name, :bank_code, :bank_code_hashed,
8
+ :bank_availability
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Returned by direct-debit enrolment, maintenance and termination calls.
6
+ class FpxDirectDebitApplicationResource < Resource
7
+ attributes :payer_name, :payer_id_type, :payer_id, :payer_email,
8
+ :payer_telephone_number, :order_number, :amount, :application_type,
9
+ :application_reason, :frequency_mode, :effective_date, :expiry_date, :url
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Returned by {Bayarcash::Client#get_fpx_direct_debit} (a mandate).
6
+ class FpxDirectDebitResource < Resource
7
+ attributes :id, :updated_at, :mandate_reference_number, :order_number,
8
+ :application_reason, :frequency_mode, :frequency_mode_label,
9
+ :effective_date, :expiry_date, :currency, :amount, :payer_name,
10
+ :payer_id, :payer_id_type, :payer_bank_account_number, :payer_email,
11
+ :payer_telephone_number, :status, :status_description, :return_url,
12
+ :metadata, :portal, :merchant
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Returned by {Bayarcash::Client#create_payment_intent} and
6
+ # {Bayarcash::Client#get_payment_intent}.
7
+ class PaymentIntentResource < Resource
8
+ attributes :payer_name, :payer_email, :payer_telephone_number, :order_number,
9
+ :amount, :url, :type, :id, :status, :last_attempt, :paid_at,
10
+ :currency, :attempts
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Returned by {Bayarcash::Client#get_portals}.
6
+ class PortalResource < Resource
7
+ attributes :id, :created_at, :portal_key, :portal_name, :website_url,
8
+ :transaction_notification_email, :secondary_transaction_notification_email,
9
+ :custom_payment_button_text, :enabled_sms_on_successful_transaction,
10
+ :split_payment_enabled, :split_payment_merchants, :payment_channels,
11
+ :merchant, :url, :merchant_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Base data-transfer object for API responses.
6
+ #
7
+ # Attributes are populated from the (snake_case) keys of the API payload and
8
+ # exposed as reader methods. Declared attributes always respond (returning nil
9
+ # when the API omits them); any unknown field the API returns is captured too,
10
+ # so the DTOs are tolerant of new or missing fields.
11
+ class Resource
12
+ # Declare known attributes for a resource subclass. Each becomes a reader that
13
+ # returns nil when absent.
14
+ #
15
+ # @param names [Array<Symbol, String>]
16
+ def self.attributes(*names)
17
+ @declared_attributes ||= []
18
+ names.each do |name|
19
+ key = name.to_s
20
+ @declared_attributes << key unless @declared_attributes.include?(key)
21
+ define_method(key) { @attributes[key] }
22
+ end
23
+ end
24
+
25
+ # @return [Array<String>] declared attribute names, including inherited ones
26
+ def self.declared_attributes
27
+ inherited = superclass.respond_to?(:declared_attributes) ? superclass.declared_attributes : []
28
+ inherited + (@declared_attributes || [])
29
+ end
30
+
31
+ # @param attributes [Hash] the API payload
32
+ # @param bayarcash [Bayarcash::Client, nil] owning client (never serialized)
33
+ def initialize(attributes = {}, bayarcash = nil)
34
+ @bayarcash = bayarcash
35
+ @attributes = {}
36
+ fill(attributes)
37
+ end
38
+
39
+ # Read a raw attribute by key (string or symbol).
40
+ #
41
+ # @param key [String, Symbol]
42
+ # @return [Object, nil]
43
+ def [](key)
44
+ @attributes[key.to_s]
45
+ end
46
+
47
+ # @return [Hash{String => Object}] the attributes, with nested resources converted
48
+ def to_h
49
+ @attributes.each_with_object({}) do |(key, value), memo|
50
+ memo[key] = deep_to_h(value)
51
+ end
52
+ end
53
+ alias to_array to_h
54
+ alias to_hash to_h
55
+
56
+ def respond_to_missing?(name, include_private = false)
57
+ key = name.to_s
58
+ @attributes.key?(key) || self.class.declared_attributes.include?(key) || super
59
+ end
60
+
61
+ def method_missing(name, *args)
62
+ key = name.to_s
63
+ if @attributes.key?(key) || self.class.declared_attributes.include?(key)
64
+ @attributes[key]
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def fill(attributes)
73
+ (attributes || {}).each do |key, value|
74
+ @attributes[key.to_s] = value
75
+ end
76
+ end
77
+
78
+ def deep_to_h(value)
79
+ case value
80
+ when Resource
81
+ value.to_h
82
+ when Array
83
+ value.map { |item| item.is_a?(Resource) ? item.to_h : item }
84
+ else
85
+ value
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bayarcash
4
+ module Resources
5
+ # Returned by transaction lookups and queries.
6
+ class TransactionResource < Resource
7
+ attributes :id, :updated_at, :created_at, :datetime, :payer_name, :payer_email,
8
+ :payer_telephone_number, :order_number, :currency, :amount,
9
+ :exchange_reference_number, :exchange_transaction_id, :payer_bank_name,
10
+ :status, :status_description, :return_url, :metadata, :payout,
11
+ :payment_gateway, :portal, :merchant, :mandate
12
+ end
13
+ end
14
+ end