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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE +21 -0
- data/README.md +517 -0
- data/bayarcash.gemspec +32 -0
- data/lib/bayarcash/callback_verifications.rb +177 -0
- data/lib/bayarcash/checksum_generator.rb +110 -0
- data/lib/bayarcash/client.rb +293 -0
- data/lib/bayarcash/duit_now/dobw.rb +33 -0
- data/lib/bayarcash/errors.rb +49 -0
- data/lib/bayarcash/fpx.rb +26 -0
- data/lib/bayarcash/fpx_direct_debit.rb +75 -0
- data/lib/bayarcash/fpx_direct_debit_payment_intent.rb +70 -0
- data/lib/bayarcash/makes_http_requests.rb +143 -0
- data/lib/bayarcash/manual_bank_transfer.rb +224 -0
- data/lib/bayarcash/resources/fpx_bank_resource.rb +11 -0
- data/lib/bayarcash/resources/fpx_direct_debit_application_resource.rb +12 -0
- data/lib/bayarcash/resources/fpx_direct_debit_resource.rb +15 -0
- data/lib/bayarcash/resources/payment_intent_resource.rb +13 -0
- data/lib/bayarcash/resources/portal_resource.rb +14 -0
- data/lib/bayarcash/resources/resource.rb +90 -0
- data/lib/bayarcash/resources/transaction_resource.rb +14 -0
- data/lib/bayarcash/security_utils.rb +37 -0
- data/lib/bayarcash/util.rb +92 -0
- data/lib/bayarcash/version.rb +5 -0
- data/lib/bayarcash.rb +41 -0
- metadata +152 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bayarcash
|
|
4
|
+
# Callback verification, one verifier per callback type.
|
|
5
|
+
#
|
|
6
|
+
# Each verifier reconstructs the exact payload the gateway signs, computes the
|
|
7
|
+
# HMAC-SHA256 checksum and compares it against the supplied `checksum` using a
|
|
8
|
+
# constant-time comparison (see {Bayarcash::SecurityUtils.secure_compare}).
|
|
9
|
+
module CallbackVerifications
|
|
10
|
+
# Verify a direct-debit bank-approval callback.
|
|
11
|
+
#
|
|
12
|
+
# @param callback_data [Hash]
|
|
13
|
+
# @param secret_key [String]
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def verify_direct_debit_bank_approval_callback_data(callback_data, secret_key)
|
|
16
|
+
payload = {
|
|
17
|
+
"record_type" => cb(callback_data, "record_type"),
|
|
18
|
+
"approval_date" => cb(callback_data, "approval_date"),
|
|
19
|
+
"approval_status" => cb(callback_data, "approval_status"),
|
|
20
|
+
"mandate_id" => cb(callback_data, "mandate_id"),
|
|
21
|
+
"mandate_reference_number" => cb(callback_data, "mandate_reference_number"),
|
|
22
|
+
"order_number" => cb(callback_data, "order_number"),
|
|
23
|
+
"payer_bank_code_hashed" => cb(callback_data, "payer_bank_code_hashed"),
|
|
24
|
+
"payer_bank_code" => cb(callback_data, "payer_bank_code"),
|
|
25
|
+
"payer_bank_account_no" => cb(callback_data, "payer_bank_account_no"),
|
|
26
|
+
"application_type" => cb(callback_data, "application_type")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
verify_callback(callback_data, payload, secret_key)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Verify a direct-debit authorization callback.
|
|
33
|
+
#
|
|
34
|
+
# NOTE: this payload MUST include `application_type` (the gateway signs it);
|
|
35
|
+
# omitting it causes every genuine authorization callback to fail verification.
|
|
36
|
+
#
|
|
37
|
+
# @param callback_data [Hash]
|
|
38
|
+
# @param secret_key [String]
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def verify_direct_debit_authorization_callback_data(callback_data, secret_key)
|
|
41
|
+
payload = {
|
|
42
|
+
"record_type" => cb(callback_data, "record_type"),
|
|
43
|
+
"transaction_id" => cb(callback_data, "transaction_id"),
|
|
44
|
+
"mandate_id" => cb(callback_data, "mandate_id"),
|
|
45
|
+
"application_type" => cb(callback_data, "application_type"),
|
|
46
|
+
"exchange_reference_number" => cb(callback_data, "exchange_reference_number"),
|
|
47
|
+
"exchange_transaction_id" => cb(callback_data, "exchange_transaction_id"),
|
|
48
|
+
"order_number" => cb(callback_data, "order_number"),
|
|
49
|
+
"currency" => cb(callback_data, "currency"),
|
|
50
|
+
"amount" => cb(callback_data, "amount"),
|
|
51
|
+
"payer_name" => cb(callback_data, "payer_name"),
|
|
52
|
+
"payer_email" => cb(callback_data, "payer_email"),
|
|
53
|
+
"payer_bank_name" => cb(callback_data, "payer_bank_name"),
|
|
54
|
+
"status" => cb(callback_data, "status"),
|
|
55
|
+
"status_description" => cb(callback_data, "status_description"),
|
|
56
|
+
"datetime" => cb(callback_data, "datetime")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
verify_callback(callback_data, payload, secret_key)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Verify a direct-debit transaction callback.
|
|
63
|
+
#
|
|
64
|
+
# @param callback_data [Hash]
|
|
65
|
+
# @param secret_key [String]
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def verify_direct_debit_transaction_callback_data(callback_data, secret_key)
|
|
68
|
+
payload = {
|
|
69
|
+
"record_type" => cb(callback_data, "record_type"),
|
|
70
|
+
"batch_number" => cb(callback_data, "batch_number"),
|
|
71
|
+
"mandate_id" => cb(callback_data, "mandate_id"),
|
|
72
|
+
"mandate_reference_number" => cb(callback_data, "mandate_reference_number"),
|
|
73
|
+
"transaction_id" => cb(callback_data, "transaction_id"),
|
|
74
|
+
"datetime" => cb(callback_data, "datetime"),
|
|
75
|
+
"reference_number" => cb(callback_data, "reference_number"),
|
|
76
|
+
"amount" => cb(callback_data, "amount"),
|
|
77
|
+
"status" => cb(callback_data, "status"),
|
|
78
|
+
"status_description" => cb(callback_data, "status_description"),
|
|
79
|
+
"cycle" => cb(callback_data, "cycle")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
verify_callback(callback_data, payload, secret_key)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Verify a transaction callback (sent to your callback_url).
|
|
86
|
+
#
|
|
87
|
+
# @param callback_data [Hash]
|
|
88
|
+
# @param secret_key [String]
|
|
89
|
+
# @return [Boolean]
|
|
90
|
+
def verify_transaction_callback_data(callback_data, secret_key)
|
|
91
|
+
payload = {
|
|
92
|
+
"record_type" => cb(callback_data, "record_type"),
|
|
93
|
+
"transaction_id" => cb(callback_data, "transaction_id"),
|
|
94
|
+
"exchange_reference_number" => cb(callback_data, "exchange_reference_number"),
|
|
95
|
+
"exchange_transaction_id" => cb(callback_data, "exchange_transaction_id"),
|
|
96
|
+
"order_number" => cb(callback_data, "order_number"),
|
|
97
|
+
"currency" => cb(callback_data, "currency"),
|
|
98
|
+
"amount" => cb(callback_data, "amount"),
|
|
99
|
+
"payer_name" => cb(callback_data, "payer_name"),
|
|
100
|
+
"payer_email" => cb(callback_data, "payer_email"),
|
|
101
|
+
"payer_bank_name" => cb(callback_data, "payer_bank_name"),
|
|
102
|
+
"status" => cb(callback_data, "status"),
|
|
103
|
+
"status_description" => cb(callback_data, "status_description"),
|
|
104
|
+
"datetime" => cb(callback_data, "datetime")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
verify_callback(callback_data, payload, secret_key)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Verify a return-url callback (payer redirect).
|
|
111
|
+
#
|
|
112
|
+
# @param callback_data [Hash]
|
|
113
|
+
# @param secret_key [String]
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
def verify_return_url_callback_data(callback_data, secret_key)
|
|
116
|
+
payload = {
|
|
117
|
+
"transaction_id" => cb(callback_data, "transaction_id"),
|
|
118
|
+
"exchange_reference_number" => cb(callback_data, "exchange_reference_number"),
|
|
119
|
+
"exchange_transaction_id" => cb(callback_data, "exchange_transaction_id"),
|
|
120
|
+
"order_number" => cb(callback_data, "order_number"),
|
|
121
|
+
"currency" => cb(callback_data, "currency"),
|
|
122
|
+
"amount" => cb(callback_data, "amount"),
|
|
123
|
+
"payer_bank_name" => cb(callback_data, "payer_bank_name"),
|
|
124
|
+
"status" => cb(callback_data, "status"),
|
|
125
|
+
"status_description" => cb(callback_data, "status_description")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
verify_callback(callback_data, payload, secret_key)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Verify a pre-transaction callback (sent before the transaction record).
|
|
132
|
+
#
|
|
133
|
+
# @param callback_data [Hash]
|
|
134
|
+
# @param secret_key [String]
|
|
135
|
+
# @return [Boolean]
|
|
136
|
+
def verify_pre_transaction_callback_data(callback_data, secret_key)
|
|
137
|
+
payload = {
|
|
138
|
+
"record_type" => cb(callback_data, "record_type"),
|
|
139
|
+
"exchange_reference_number" => cb(callback_data, "exchange_reference_number"),
|
|
140
|
+
"order_number" => cb(callback_data, "order_number")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
verify_callback(callback_data, payload, secret_key)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
# Reconstruct the checksum from the payload and compare it, constant-time,
|
|
149
|
+
# against the checksum supplied in the callback data.
|
|
150
|
+
def verify_callback(callback_data, payload, secret_key)
|
|
151
|
+
provided_checksum = cb(callback_data, "checksum") || ""
|
|
152
|
+
|
|
153
|
+
payload_string = payload
|
|
154
|
+
.sort_by { |key, _| key.to_s }
|
|
155
|
+
.map { |_, value| Bayarcash::Util.php_string(value) }
|
|
156
|
+
.join("|")
|
|
157
|
+
|
|
158
|
+
computed = OpenSSL::HMAC.hexdigest(
|
|
159
|
+
"SHA256",
|
|
160
|
+
Bayarcash::Util.php_string(secret_key),
|
|
161
|
+
payload_string
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
Bayarcash::SecurityUtils.secure_compare(computed, provided_checksum.to_s)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Read a callback field that may use string or symbol keys; nil when absent.
|
|
168
|
+
def cb(callback_data, key)
|
|
169
|
+
return callback_data[key] if callback_data.key?(key)
|
|
170
|
+
|
|
171
|
+
symbol = key.to_sym
|
|
172
|
+
return callback_data[symbol] if callback_data.key?(symbol)
|
|
173
|
+
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bayarcash
|
|
4
|
+
# Checksum generation (HMAC-SHA256), byte-compatible with the gateway.
|
|
5
|
+
#
|
|
6
|
+
# The recipe is always the same: sort the payload hash by KEY ascending, join the
|
|
7
|
+
# VALUES with "|", then HMAC-SHA256 with the merchant secret key and return the
|
|
8
|
+
# lowercase hex digest.
|
|
9
|
+
module ChecksumGenerator
|
|
10
|
+
# Generic checksum primitive: ksort by key, implode values with "|", HMAC-SHA256.
|
|
11
|
+
#
|
|
12
|
+
# @param secret_key [String]
|
|
13
|
+
# @param payload [Hash]
|
|
14
|
+
# @return [String] lowercase hex HMAC-SHA256 digest
|
|
15
|
+
def create_checksum_value(secret_key, payload)
|
|
16
|
+
hmac_from_payload(secret_key, payload)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Checksum for a payment intent request.
|
|
20
|
+
#
|
|
21
|
+
# Signs `payment_channel` (comma-joined when an array/multiple, empty when none),
|
|
22
|
+
# `order_number`, `amount`, `payer_name` and `payer_email`.
|
|
23
|
+
#
|
|
24
|
+
# @param secret_key [String]
|
|
25
|
+
# @param data [Hash]
|
|
26
|
+
# @return [String]
|
|
27
|
+
def create_payment_intent_checksum_value(secret_key, data)
|
|
28
|
+
payment_channel = fetch_field(data, "payment_channel", [])
|
|
29
|
+
payment_channel = [payment_channel] unless payment_channel.is_a?(Array)
|
|
30
|
+
payment_channel = payment_channel.join(",")
|
|
31
|
+
|
|
32
|
+
payload = {
|
|
33
|
+
"payment_channel" => payment_channel,
|
|
34
|
+
"order_number" => fetch_field(data, "order_number"),
|
|
35
|
+
"amount" => fetch_field(data, "amount"),
|
|
36
|
+
"payer_name" => fetch_field(data, "payer_name"),
|
|
37
|
+
"payer_email" => fetch_field(data, "payer_email")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
hmac_from_payload(secret_key, payload)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Old typo version, kept for backward compatibility.
|
|
44
|
+
#
|
|
45
|
+
# @see #create_payment_intent_checksum_value
|
|
46
|
+
def create_payment_inten_checksum_value(secret_key, data)
|
|
47
|
+
create_payment_intent_checksum_value(secret_key, data)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Checksum for an FPX Direct Debit enrolment request.
|
|
51
|
+
#
|
|
52
|
+
# @param secret_key [String]
|
|
53
|
+
# @param data [Hash]
|
|
54
|
+
# @return [String]
|
|
55
|
+
def create_fpx_direct_debit_enrolment_checksum_value(secret_key, data)
|
|
56
|
+
payload = {
|
|
57
|
+
"order_number" => fetch_field(data, "order_number"),
|
|
58
|
+
"amount" => fetch_field(data, "amount"),
|
|
59
|
+
"payer_name" => fetch_field(data, "payer_name"),
|
|
60
|
+
"payer_email" => fetch_field(data, "payer_email"),
|
|
61
|
+
"payer_telephone_number" => fetch_field(data, "payer_telephone_number"),
|
|
62
|
+
"payer_id_type" => fetch_field(data, "payer_id_type"),
|
|
63
|
+
"payer_id" => fetch_field(data, "payer_id"),
|
|
64
|
+
"application_reason" => fetch_field(data, "application_reason"),
|
|
65
|
+
"frequency_mode" => fetch_field(data, "frequency_mode")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
hmac_from_payload(secret_key, payload)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Checksum for an FPX Direct Debit maintenance request.
|
|
72
|
+
#
|
|
73
|
+
# @param secret_key [String]
|
|
74
|
+
# @param data [Hash]
|
|
75
|
+
# @return [String]
|
|
76
|
+
def create_fpx_direct_debit_maintenance_checksum_value(secret_key, data)
|
|
77
|
+
payload = {
|
|
78
|
+
"amount" => fetch_field(data, "amount"),
|
|
79
|
+
"payer_email" => fetch_field(data, "payer_email"),
|
|
80
|
+
"payer_telephone_number" => fetch_field(data, "payer_telephone_number"),
|
|
81
|
+
"application_reason" => fetch_field(data, "application_reason"),
|
|
82
|
+
"frequency_mode" => fetch_field(data, "frequency_mode")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hmac_from_payload(secret_key, payload)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Sort the payload by key, join stringified values with "|", HMAC-SHA256.
|
|
91
|
+
def hmac_from_payload(secret_key, payload)
|
|
92
|
+
payload_string = payload
|
|
93
|
+
.sort_by { |key, _| key.to_s }
|
|
94
|
+
.map { |_, value| Bayarcash::Util.php_string(value) }
|
|
95
|
+
.join("|")
|
|
96
|
+
|
|
97
|
+
OpenSSL::HMAC.hexdigest("SHA256", Bayarcash::Util.php_string(secret_key), payload_string)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Read a field from a hash that may use string or symbol keys.
|
|
101
|
+
def fetch_field(data, key, default = nil)
|
|
102
|
+
return data[key] if data.key?(key)
|
|
103
|
+
|
|
104
|
+
symbol = key.to_sym
|
|
105
|
+
return data[symbol] if data.key?(symbol)
|
|
106
|
+
|
|
107
|
+
default
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bayarcash
|
|
4
|
+
# The Bayarcash API client.
|
|
5
|
+
#
|
|
6
|
+
# Mirrors the public surface of the official PHP SDK while remaining idiomatic
|
|
7
|
+
# Ruby and framework-agnostic.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# client = Bayarcash::Client.new("YOUR_API_TOKEN")
|
|
11
|
+
# client.use_sandbox.set_api_version("v3")
|
|
12
|
+
# intent = client.create_payment_intent(data)
|
|
13
|
+
# redirect_to intent.url
|
|
14
|
+
class Client
|
|
15
|
+
include MakesHttpRequests
|
|
16
|
+
include ChecksumGenerator
|
|
17
|
+
include CallbackVerifications
|
|
18
|
+
include FpxDirectDebitPaymentIntent
|
|
19
|
+
include ManualBankTransfer
|
|
20
|
+
|
|
21
|
+
# Payment channels
|
|
22
|
+
FPX = 1
|
|
23
|
+
MANUAL_TRANSFER = 2
|
|
24
|
+
FPX_DIRECT_DEBIT = 3
|
|
25
|
+
FPX_LINE_OF_CREDIT = 4
|
|
26
|
+
DUITNOW_DOBW = 5
|
|
27
|
+
DUITNOW_QR = 6
|
|
28
|
+
SPAYLATER = 7
|
|
29
|
+
BOOST_PAYFLEX = 8
|
|
30
|
+
QRISOB = 9
|
|
31
|
+
QRISWALLET = 10
|
|
32
|
+
NETS = 11
|
|
33
|
+
CREDIT_CARD = 12
|
|
34
|
+
ALIPAY = 13
|
|
35
|
+
WECHATPAY = 14
|
|
36
|
+
PROMPTPAY = 15
|
|
37
|
+
TOUCH_N_GO = 16
|
|
38
|
+
BOOST_WALLET = 17
|
|
39
|
+
GRABPAY = 18
|
|
40
|
+
GRABPL = 19
|
|
41
|
+
SHOPEE_PAY = 21
|
|
42
|
+
|
|
43
|
+
# Allowed filter keys for {#get_all_transactions}.
|
|
44
|
+
ALLOWED_TRANSACTION_FILTERS = %w[
|
|
45
|
+
order_number status payment_channel exchange_reference_number payer_email
|
|
46
|
+
].freeze
|
|
47
|
+
|
|
48
|
+
# @return [String] the API token
|
|
49
|
+
attr_reader :token
|
|
50
|
+
# @return [Integer] request timeout in seconds
|
|
51
|
+
attr_reader :timeout
|
|
52
|
+
# @return [Boolean] whether the sandbox environment is in use
|
|
53
|
+
attr_reader :sandbox
|
|
54
|
+
# @return [String] the API version in use ("v2" or "v3")
|
|
55
|
+
attr_reader :api_version
|
|
56
|
+
|
|
57
|
+
# @param token [String] the Bayarcash API token
|
|
58
|
+
# @param sandbox [Boolean] use the sandbox environment
|
|
59
|
+
# @param api_version [String] "v2" (default) or "v3"
|
|
60
|
+
# @param timeout [Integer] request timeout in seconds
|
|
61
|
+
def initialize(token, sandbox: false, api_version: "v2", timeout: 30)
|
|
62
|
+
@token = token
|
|
63
|
+
@sandbox = sandbox
|
|
64
|
+
@api_version = api_version
|
|
65
|
+
@timeout = timeout
|
|
66
|
+
build_connection
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Set the API token and rebuild the HTTP client.
|
|
70
|
+
#
|
|
71
|
+
# @param token [String]
|
|
72
|
+
# @param connection [Faraday::Connection, nil] inject a custom connection (mainly for tests)
|
|
73
|
+
# @return [self]
|
|
74
|
+
def set_token(token, connection = nil)
|
|
75
|
+
@token = token
|
|
76
|
+
connection ? (@connection = connection) : build_connection
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Switch to the sandbox environment and rebuild the HTTP client.
|
|
81
|
+
#
|
|
82
|
+
# @param connection [Faraday::Connection, nil]
|
|
83
|
+
# @return [self]
|
|
84
|
+
def use_sandbox(connection = nil)
|
|
85
|
+
@sandbox = true
|
|
86
|
+
connection ? (@connection = connection) : build_connection
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Boolean]
|
|
91
|
+
def sandbox?
|
|
92
|
+
@sandbox
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Set the request timeout and rebuild the HTTP client.
|
|
96
|
+
#
|
|
97
|
+
# @param timeout [Integer] seconds
|
|
98
|
+
# @return [self]
|
|
99
|
+
def set_timeout(timeout)
|
|
100
|
+
@timeout = timeout
|
|
101
|
+
build_connection
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [Integer]
|
|
106
|
+
def get_timeout
|
|
107
|
+
@timeout
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Set the API version ("v2" or "v3") and rebuild the HTTP client.
|
|
111
|
+
#
|
|
112
|
+
# @param version [String]
|
|
113
|
+
# @return [self]
|
|
114
|
+
def set_api_version(version)
|
|
115
|
+
@api_version = version
|
|
116
|
+
build_connection
|
|
117
|
+
self
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [String]
|
|
121
|
+
def get_api_version
|
|
122
|
+
@api_version
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get the list of FPX banks.
|
|
126
|
+
#
|
|
127
|
+
# @return [Array<Bayarcash::Resources::FpxBankResource>]
|
|
128
|
+
def fpx_banks_list
|
|
129
|
+
transform_collection(get("banks"), Resources::FpxBankResource)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get the list of portals.
|
|
133
|
+
#
|
|
134
|
+
# @return [Array<Bayarcash::Resources::PortalResource>]
|
|
135
|
+
def get_portals
|
|
136
|
+
response = get("portals")
|
|
137
|
+
collection = response.is_a?(Hash) ? (response["data"] || response) : response
|
|
138
|
+
transform_collection(collection, Resources::PortalResource)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Get the payment channels available for a portal by portal key.
|
|
142
|
+
#
|
|
143
|
+
# @param portal_key [String]
|
|
144
|
+
# @return [Array]
|
|
145
|
+
def get_channels(portal_key)
|
|
146
|
+
get_portals.each do |portal|
|
|
147
|
+
return portal.payment_channels if portal.portal_key == portal_key
|
|
148
|
+
end
|
|
149
|
+
[]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Create a new payment intent.
|
|
153
|
+
#
|
|
154
|
+
# @param data [Hash]
|
|
155
|
+
# @return [Bayarcash::Resources::PaymentIntentResource]
|
|
156
|
+
def create_payment_intent(data)
|
|
157
|
+
Resources::PaymentIntentResource.new(post("payment-intents", data), self)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Get transaction details (v2 and v3).
|
|
161
|
+
#
|
|
162
|
+
# @param id [String]
|
|
163
|
+
# @return [Bayarcash::Resources::TransactionResource]
|
|
164
|
+
def get_transaction(id)
|
|
165
|
+
Resources::TransactionResource.new(get("transactions/#{id}"), self)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Get a payment intent by id (v3 only).
|
|
169
|
+
#
|
|
170
|
+
# @param payment_intent_id [String]
|
|
171
|
+
# @return [Bayarcash::Resources::PaymentIntentResource]
|
|
172
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
173
|
+
def get_payment_intent(payment_intent_id)
|
|
174
|
+
ensure_v3!("getPaymentIntent")
|
|
175
|
+
Resources::PaymentIntentResource.new(get("payment-intents/#{payment_intent_id}"), self)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Cancel a payment intent (v3 only).
|
|
179
|
+
#
|
|
180
|
+
# @param payment_intent_id [String]
|
|
181
|
+
# @return [Bayarcash::Resources::PaymentIntentResource]
|
|
182
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
183
|
+
def cancel_payment_intent(payment_intent_id)
|
|
184
|
+
ensure_v3!("cancelPaymentIntent")
|
|
185
|
+
Resources::PaymentIntentResource.new(delete("payment-intents/#{payment_intent_id}"), self)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Get all transactions with optional filters (v3 only).
|
|
189
|
+
#
|
|
190
|
+
# @param parameters [Hash] any of :order_number, :status, :payment_channel,
|
|
191
|
+
# :exchange_reference_number, :payer_email
|
|
192
|
+
# @return [Hash] { data: Array<TransactionResource>, meta: Hash }
|
|
193
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
194
|
+
def get_all_transactions(parameters = {})
|
|
195
|
+
ensure_v3!("getAllTransactions")
|
|
196
|
+
|
|
197
|
+
query_params = parameters.select { |key, _| ALLOWED_TRANSACTION_FILTERS.include?(key.to_s) }
|
|
198
|
+
query_string = Bayarcash::Util.build_query(query_params)
|
|
199
|
+
endpoint = "transactions" + (query_string.empty? ? "" : "?#{query_string}")
|
|
200
|
+
|
|
201
|
+
response = get(endpoint)
|
|
202
|
+
|
|
203
|
+
{
|
|
204
|
+
data: transform_collection(response.is_a?(Hash) ? (response["data"] || []) : [], Resources::TransactionResource),
|
|
205
|
+
meta: response.is_a?(Hash) ? (response["meta"] || {}) : {}
|
|
206
|
+
}
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Get transactions by order number (v3 only).
|
|
210
|
+
#
|
|
211
|
+
# @param order_number [String]
|
|
212
|
+
# @return [Array<Bayarcash::Resources::TransactionResource>]
|
|
213
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
214
|
+
def get_transaction_by_order_number(order_number)
|
|
215
|
+
ensure_v3!("getTransactionByOrderNumber")
|
|
216
|
+
response = get("transactions?order_number=#{order_number}")
|
|
217
|
+
transform_collection(response.is_a?(Hash) ? (response["data"] || []) : [], Resources::TransactionResource)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Get transactions by payer email (v3 only).
|
|
221
|
+
#
|
|
222
|
+
# @param email [String]
|
|
223
|
+
# @return [Array<Bayarcash::Resources::TransactionResource>]
|
|
224
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
225
|
+
def get_transactions_by_payer_email(email)
|
|
226
|
+
ensure_v3!("getTransactionsByPayerEmail")
|
|
227
|
+
response = get("transactions?payer_email=#{Bayarcash::Util.url_encode(email)}")
|
|
228
|
+
transform_collection(response.is_a?(Hash) ? (response["data"] || []) : [], Resources::TransactionResource)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Get transactions by status (v3 only).
|
|
232
|
+
#
|
|
233
|
+
# @param status [String]
|
|
234
|
+
# @return [Array<Bayarcash::Resources::TransactionResource>]
|
|
235
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
236
|
+
def get_transactions_by_status(status)
|
|
237
|
+
ensure_v3!("getTransactionsByStatus")
|
|
238
|
+
response = get("transactions?status=#{status}")
|
|
239
|
+
transform_collection(response.is_a?(Hash) ? (response["data"] || []) : [], Resources::TransactionResource)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Get transactions by payment channel (v3 only).
|
|
243
|
+
#
|
|
244
|
+
# @param channel [Integer]
|
|
245
|
+
# @return [Array<Bayarcash::Resources::TransactionResource>]
|
|
246
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
247
|
+
def get_transactions_by_payment_channel(channel)
|
|
248
|
+
ensure_v3!("getTransactionsByPaymentChannel")
|
|
249
|
+
response = get("transactions?payment_channel=#{channel}")
|
|
250
|
+
transform_collection(response.is_a?(Hash) ? (response["data"] || []) : [], Resources::TransactionResource)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Get a single transaction by exchange reference number (v3 only).
|
|
254
|
+
#
|
|
255
|
+
# @param reference_number [String]
|
|
256
|
+
# @return [Bayarcash::Resources::TransactionResource, nil]
|
|
257
|
+
# @raise [Bayarcash::Error] when the API version is not v3
|
|
258
|
+
def get_transaction_by_reference_number(reference_number)
|
|
259
|
+
ensure_v3!("getTransactionByReferenceNumber")
|
|
260
|
+
response = get("transactions?exchange_reference_number=#{Bayarcash::Util.url_encode(reference_number)}")
|
|
261
|
+
data = response.is_a?(Hash) ? (response["data"] || []) : []
|
|
262
|
+
return nil if data.nil? || data.empty?
|
|
263
|
+
|
|
264
|
+
transform_collection(data, Resources::TransactionResource).first
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Return the base URI for the current API version and environment.
|
|
268
|
+
#
|
|
269
|
+
# @return [String]
|
|
270
|
+
def base_uri
|
|
271
|
+
if @api_version == "v3"
|
|
272
|
+
@sandbox ? "https://api.console.bayarcash-sandbox.com/v3/" : "https://api.console.bayar.cash/v3/"
|
|
273
|
+
else
|
|
274
|
+
@sandbox ? "https://console.bayarcash-sandbox.com/api/v2/" : "https://console.bayar.cash/api/v2/"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
# Transform a raw collection into an array of resources.
|
|
281
|
+
def transform_collection(collection, klass, extra = {})
|
|
282
|
+
(collection || []).map do |data|
|
|
283
|
+
klass.new((data || {}).merge(extra), self)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def ensure_v3!(method_name)
|
|
288
|
+
return if @api_version == "v3"
|
|
289
|
+
|
|
290
|
+
raise Bayarcash::Error, "The #{method_name} method is only available for API version v3."
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bayarcash
|
|
4
|
+
module DuitNow
|
|
5
|
+
# DuitNow Online Banking / Wallet (DOBW) account types and status codes.
|
|
6
|
+
class Dobw
|
|
7
|
+
CASA = "01"
|
|
8
|
+
CREDIT_CARD = "02"
|
|
9
|
+
EWALLET = "03"
|
|
10
|
+
|
|
11
|
+
# Status code
|
|
12
|
+
STATUS_NEW = 0
|
|
13
|
+
STATUS_PENDING = 1
|
|
14
|
+
STATUS_FAILED = 2
|
|
15
|
+
STATUS_SUCCESS = 3
|
|
16
|
+
STATUS_CANCELLED = 4
|
|
17
|
+
|
|
18
|
+
STATUS_LABELS = {
|
|
19
|
+
STATUS_NEW => "New",
|
|
20
|
+
STATUS_PENDING => "Pending",
|
|
21
|
+
STATUS_CANCELLED => "Cancelled",
|
|
22
|
+
STATUS_SUCCESS => "Successful",
|
|
23
|
+
STATUS_FAILED => "Failed"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# @param status_code [Integer]
|
|
27
|
+
# @return [String] label for the status, or "UNKNOWN STATUS"
|
|
28
|
+
def self.get_status_text(status_code)
|
|
29
|
+
STATUS_LABELS.fetch(status_code.to_i, "UNKNOWN STATUS")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bayarcash
|
|
4
|
+
# Base class for every error raised by the SDK.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised on HTTP 422 responses. Carries the validation error payload from the API.
|
|
8
|
+
class ValidationError < Error
|
|
9
|
+
# @return [Hash, Array] the raw errors payload returned by the API
|
|
10
|
+
attr_reader :errors
|
|
11
|
+
|
|
12
|
+
def initialize(errors = {})
|
|
13
|
+
@errors = errors || {}
|
|
14
|
+
super("The given data failed to pass validation.")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Raised on HTTP 404 responses.
|
|
19
|
+
class NotFoundError < Error
|
|
20
|
+
def initialize(message = "The resource you are looking for could not be found.")
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Raised on HTTP 400 responses. The message holds the reason returned by the API.
|
|
26
|
+
class FailedActionError < Error; end
|
|
27
|
+
|
|
28
|
+
# Raised on HTTP 429 responses. Exposes the rate-limit reset timestamp when available.
|
|
29
|
+
class RateLimitExceededError < Error
|
|
30
|
+
# @return [Integer, nil] unix timestamp at which the rate limit resets
|
|
31
|
+
attr_reader :rate_limit_resets_at
|
|
32
|
+
|
|
33
|
+
def initialize(rate_limit_reset = nil)
|
|
34
|
+
@rate_limit_resets_at = rate_limit_reset
|
|
35
|
+
super("Too Many Requests.")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Raised by the optional {Bayarcash::MakesHttpRequests#retry_until} helper after a timeout.
|
|
40
|
+
class TimeoutError < Error
|
|
41
|
+
# @return [Array] the output collected before the timeout
|
|
42
|
+
attr_reader :output
|
|
43
|
+
|
|
44
|
+
def initialize(output = [])
|
|
45
|
+
@output = output
|
|
46
|
+
super("Script timed out while waiting for the process to complete.")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bayarcash
|
|
4
|
+
# FPX transaction status codes and their human-readable labels.
|
|
5
|
+
class Fpx
|
|
6
|
+
STATUS_NEW = 0
|
|
7
|
+
STATUS_PENDING = 1
|
|
8
|
+
STATUS_FAILED = 2
|
|
9
|
+
STATUS_SUCCESS = 3
|
|
10
|
+
STATUS_CANCELLED = 4
|
|
11
|
+
|
|
12
|
+
STATUS_LABELS = {
|
|
13
|
+
STATUS_NEW => "New",
|
|
14
|
+
STATUS_PENDING => "Pending",
|
|
15
|
+
STATUS_CANCELLED => "Cancelled",
|
|
16
|
+
STATUS_SUCCESS => "Successful",
|
|
17
|
+
STATUS_FAILED => "Failed"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# @param status_code [Integer]
|
|
21
|
+
# @return [String] label for the status, or "UNKNOWN STATUS"
|
|
22
|
+
def self.get_status_text(status_code)
|
|
23
|
+
STATUS_LABELS.fetch(status_code.to_i, "UNKNOWN STATUS")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|