airwallex 0.3.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +33 -8
- data/README.md +2 -2
- data/lib/airwallex/client.rb +14 -10
- data/lib/airwallex/middleware/auth_refresh.rb +21 -7
- data/lib/airwallex/version.rb +1 -1
- data/lib/airwallex/webhook.rb +6 -2
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec7542a0e82153bbd60acceadb071eba2d48d328d89fa34d72d8a23dc30aeb45
|
|
4
|
+
data.tar.gz: b14a15b3b70878c6291bc6ffd447ccf81d4cab5d86e61b806cc0708bd70590e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e401e23580f1347cbf2caae60b985138c59e73a22ba758578c7ffd99b2318859f766e7c0e0a13f55bff4da29a6796029a359a12a9c1db32b213c972244e70a3
|
|
7
|
+
data.tar.gz: b7779fbdc8227ae72d20eda510799bb6898edb43c74d7aa6a6a30b7110f304be96b2e17673b02e832c2a9638c48f987b6ecf54027fc4b18f754d218fbaec4398
|
data/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-08-28
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `Webhook::Event` now correctly exposes the event type. Airwallex sends this field as `"name"` in
|
|
7
|
+
the webhook payload, not `"type"` — the previous `Event#type` reader was reading a key that doesn't
|
|
8
|
+
exist and always returned `nil`. The accessor is renamed to `Event#name` to match Airwallex's actual
|
|
9
|
+
field. **Breaking change:** `event.type` → `event.name`.
|
|
10
|
+
|
|
11
|
+
## [0.5.0] - 2026-08-28
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Webhook signature verification no longer rejects valid webhooks when Airwallex sends
|
|
15
|
+
millisecond-precision timestamps. `verify_timestamp` now detects millisecond values (anything at or
|
|
16
|
+
above `MS_THRESHOLD = 10_000_000_000`, which safely distinguishes seconds from milliseconds until the
|
|
17
|
+
year 2286) and normalizes them to seconds before comparing against the tolerance window.
|
|
18
|
+
|
|
19
|
+
## [0.4.0] - 2026-08-27
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- `Idempotency` and `AuthRefresh` middleware are now actually registered on the Faraday connection.
|
|
23
|
+
Previously both classes existed but were never wired in, so the "automatic `request_id` generation"
|
|
24
|
+
and "retry once after a 401" behavior documented in the README did not happen at runtime.
|
|
25
|
+
- Fixed a bug in `AuthRefresh` where the 401-retry guard used `env[:request][:auth_retry]`, a key that
|
|
26
|
+
doesn't exist on `Faraday::RequestOptions` and would have raised `NoMethodError` the first time it ran.
|
|
27
|
+
- CI now triggers on pushes to `main` (previously configured for `master`, so pushes never ran CI).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- `Client#request` no longer manually manages the `Authorization` header or calls
|
|
31
|
+
`ensure_authenticated!` directly; this is now owned by the `AuthRefresh` middleware.
|
|
32
|
+
|
|
3
33
|
## [0.3.0] - 2025-11-25
|
|
4
34
|
|
|
5
35
|
### Added
|
|
36
|
+
- BatchTransfer resource (create, retrieve, list) for bulk payout operations
|
|
37
|
+
- Dispute resource (retrieve, list, accept, submit_evidence) for chargeback management
|
|
6
38
|
- Foreign Exchange resources:
|
|
7
39
|
- Rate resource (retrieve, list) for real-time exchange rate queries
|
|
8
40
|
- Quote resource (create, retrieve) for locking exchange rates with expiration helpers
|
|
9
41
|
- Conversion resource (create, retrieve, list) for executing currency conversions
|
|
10
42
|
- Balance resource (list, retrieve) for querying account balances across currencies
|
|
11
43
|
- Enhanced List operation to handle both array responses and paginated responses
|
|
12
|
-
-
|
|
44
|
+
- 63 new tests (278 total) covering batch transfers, disputes, FX, and balance operations
|
|
13
45
|
- Comprehensive manual test suite for regression testing
|
|
14
46
|
|
|
15
47
|
### Changed
|
|
@@ -19,13 +51,6 @@
|
|
|
19
51
|
### Fixed
|
|
20
52
|
- List operation now correctly handles Balance API's direct array response format
|
|
21
53
|
|
|
22
|
-
## [0.2.1] - 2025-11-25
|
|
23
|
-
|
|
24
|
-
### Added
|
|
25
|
-
- BatchTransfer resource (create, retrieve, list) for bulk payout operations
|
|
26
|
-
- Dispute resource (retrieve, list, accept, submit_evidence) for chargeback management
|
|
27
|
-
- 25 new tests (240 total) covering batch transfers and disputes
|
|
28
|
-
|
|
29
54
|
## [0.2.0] - 2025-11-25
|
|
30
55
|
|
|
31
56
|
### Added
|
data/README.md
CHANGED
data/lib/airwallex/client.rb
CHANGED
|
@@ -7,6 +7,8 @@ require "json"
|
|
|
7
7
|
|
|
8
8
|
module Airwallex
|
|
9
9
|
class Client
|
|
10
|
+
LOGIN_PATH = "/api/v1/authentication/login"
|
|
11
|
+
|
|
10
12
|
attr_reader :config, :access_token, :token_expires_at
|
|
11
13
|
|
|
12
14
|
def initialize(config = Airwallex.configuration)
|
|
@@ -19,11 +21,7 @@ module Airwallex
|
|
|
19
21
|
|
|
20
22
|
def connection
|
|
21
23
|
@connection ||= Faraday.new(url: config.api_url) do |conn|
|
|
22
|
-
conn
|
|
23
|
-
conn.request :multipart
|
|
24
|
-
conn.request :retry, retry_options
|
|
25
|
-
conn.response :json, content_type: /\bjson$/
|
|
26
|
-
conn.response :logger, config.logger, { headers: true, bodies: true } if config.logger
|
|
24
|
+
configure_middleware(conn)
|
|
27
25
|
|
|
28
26
|
conn.headers["Content-Type"] = "application/json"
|
|
29
27
|
conn.headers["User-Agent"] = user_agent
|
|
@@ -55,10 +53,9 @@ module Airwallex
|
|
|
55
53
|
|
|
56
54
|
def authenticate!
|
|
57
55
|
@token_mutex.synchronize do
|
|
58
|
-
response = connection.post(
|
|
56
|
+
response = connection.post(LOGIN_PATH) do |req|
|
|
59
57
|
req.headers["x-client-id"] = config.client_id
|
|
60
58
|
req.headers["x-api-key"] = config.api_key
|
|
61
|
-
req.headers.delete("Authorization")
|
|
62
59
|
end
|
|
63
60
|
|
|
64
61
|
handle_response_errors(response)
|
|
@@ -85,12 +82,9 @@ module Airwallex
|
|
|
85
82
|
private
|
|
86
83
|
|
|
87
84
|
def request(method, path, data, headers)
|
|
88
|
-
ensure_authenticated!
|
|
89
|
-
|
|
90
85
|
response = connection.public_send(method) do |req|
|
|
91
86
|
req.url(path)
|
|
92
87
|
req.headers.merge!(headers)
|
|
93
|
-
req.headers["Authorization"] = "Bearer #{access_token}"
|
|
94
88
|
|
|
95
89
|
case method
|
|
96
90
|
when :get, :delete
|
|
@@ -104,6 +98,16 @@ module Airwallex
|
|
|
104
98
|
response.body
|
|
105
99
|
end
|
|
106
100
|
|
|
101
|
+
def configure_middleware(conn)
|
|
102
|
+
conn.use Airwallex::Middleware::Idempotency
|
|
103
|
+
conn.request :json
|
|
104
|
+
conn.request :multipart
|
|
105
|
+
conn.request :retry, retry_options
|
|
106
|
+
conn.use Airwallex::Middleware::AuthRefresh, self
|
|
107
|
+
conn.response :json, content_type: /\bjson$/
|
|
108
|
+
conn.response :logger, config.logger, { headers: true, bodies: true } if config.logger
|
|
109
|
+
end
|
|
110
|
+
|
|
107
111
|
def handle_response_errors(response)
|
|
108
112
|
return if response.success?
|
|
109
113
|
|
|
@@ -9,24 +9,38 @@ module Airwallex
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def call(env)
|
|
12
|
-
# Skip authentication
|
|
13
|
-
return @app.call(env) if
|
|
12
|
+
# Skip authentication entirely for the login endpoint itself
|
|
13
|
+
return @app.call(env) if login_request?(env)
|
|
14
14
|
|
|
15
|
-
# Ensure token is valid before making request
|
|
16
|
-
@client.ensure_authenticated! unless
|
|
15
|
+
# Ensure token is valid before making the request, then attach it
|
|
16
|
+
@client.ensure_authenticated! unless authentication_request?(env)
|
|
17
|
+
authorize!(env)
|
|
17
18
|
|
|
18
19
|
response = @app.call(env)
|
|
19
20
|
|
|
20
21
|
# If we get a 401, try refreshing the token and retrying once
|
|
21
|
-
if response.status == 401
|
|
22
|
+
if response.status == 401
|
|
22
23
|
@client.authenticate!
|
|
23
|
-
env
|
|
24
|
-
env[:request_headers]["Authorization"] = "Bearer #{@client.access_token}"
|
|
24
|
+
authorize!(env)
|
|
25
25
|
response = @app.call(env)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
response
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def login_request?(env)
|
|
34
|
+
env[:url].path.include?(Client::LOGIN_PATH)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def authentication_request?(env)
|
|
38
|
+
env[:url].path.include?("/authentication/")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def authorize!(env)
|
|
42
|
+
env[:request_headers]["Authorization"] = "Bearer #{@client.access_token}"
|
|
43
|
+
end
|
|
30
44
|
end
|
|
31
45
|
end
|
|
32
46
|
end
|
data/lib/airwallex/version.rb
CHANGED
data/lib/airwallex/webhook.rb
CHANGED
|
@@ -5,6 +5,8 @@ require "openssl"
|
|
|
5
5
|
module Airwallex
|
|
6
6
|
module Webhook
|
|
7
7
|
DEFAULT_TOLERANCE = 300 # 5 minutes
|
|
8
|
+
# Timestamps below this are seconds, at/above are milliseconds. Valid until year 2286.
|
|
9
|
+
MS_THRESHOLD = 10_000_000_000
|
|
8
10
|
|
|
9
11
|
module_function
|
|
10
12
|
|
|
@@ -37,6 +39,8 @@ module Airwallex
|
|
|
37
39
|
def verify_timestamp(timestamp, tolerance)
|
|
38
40
|
current_time = Time.now.to_i
|
|
39
41
|
timestamp_int = timestamp.to_i
|
|
42
|
+
# Airwallex sends x-timestamp in milliseconds; normalize to seconds before comparing.
|
|
43
|
+
timestamp_int /= 1000 if timestamp_int > MS_THRESHOLD
|
|
40
44
|
|
|
41
45
|
if (current_time - timestamp_int).abs > tolerance
|
|
42
46
|
raise SignatureVerificationError, "Timestamp outside tolerance (#{tolerance}s)"
|
|
@@ -54,11 +58,11 @@ module Airwallex
|
|
|
54
58
|
private_class_method :compute_signature, :verify_timestamp, :secure_compare
|
|
55
59
|
|
|
56
60
|
class Event
|
|
57
|
-
attr_reader :id, :
|
|
61
|
+
attr_reader :id, :name, :data, :created_at
|
|
58
62
|
|
|
59
63
|
def initialize(attributes = {})
|
|
60
64
|
@id = attributes["id"]
|
|
61
|
-
@
|
|
65
|
+
@name = attributes["name"]
|
|
62
66
|
@data = attributes["data"]
|
|
63
67
|
@created_at = attributes["created_at"]
|
|
64
68
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: airwallex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chayut Orapinpatipat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -122,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: '0'
|
|
124
124
|
requirements: []
|
|
125
|
-
rubygems_version: 3.
|
|
125
|
+
rubygems_version: 3.4.10
|
|
126
126
|
signing_key:
|
|
127
127
|
specification_version: 4
|
|
128
128
|
summary: Production-grade Ruby client for the Airwallex API
|