airwallex 0.2.0 → 0.3.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.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airwallex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chayut Orapinpatipat
@@ -53,9 +53,10 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.0'
55
55
  description: A comprehensive Ruby gem for integrating with Airwallex's global payment
56
- infrastructure, including payment acceptance, payouts, foreign exchange, card issuing,
57
- and treasury. Features automatic authentication management, idempotency guarantees,
58
- webhook verification, and unified pagination.
56
+ infrastructure, including payment acceptance, payouts, foreign exchange (FX rates,
57
+ quotes, conversions), and multi-currency balance management. Features automatic
58
+ authentication, idempotency guarantees, webhook verification, and unified pagination
59
+ across all resources.
59
60
  email:
60
61
  - chayut@sentia.com.au
61
62
  executables: []
@@ -66,16 +67,6 @@ files:
66
67
  - LICENSE.txt
67
68
  - README.md
68
69
  - Rakefile
69
- - docs/internal/20251125_iteration_1_quickstart.md
70
- - docs/internal/20251125_iteration_1_summary.md
71
- - docs/internal/20251125_sprint_1_completed.md
72
- - docs/internal/20251125_sprint_1_plan.md
73
- - docs/internal/20251125_sprint_2_completed.md
74
- - docs/internal/20251125_sprint_2_plan.md
75
- - docs/internal/20251125_sprint_2_unit_tests_completed.md
76
- - docs/internal/20251125_v0.1.0_publication_checklist.md
77
- - docs/research/Airwallex API Endpoint Research.md
78
- - docs/research/Airwallex API Research for Ruby Gem.md
79
70
  - lib/airwallex.rb
80
71
  - lib/airwallex/api_operations/create.rb
81
72
  - lib/airwallex/api_operations/delete.rb
@@ -89,8 +80,17 @@ files:
89
80
  - lib/airwallex/list_object.rb
90
81
  - lib/airwallex/middleware/auth_refresh.rb
91
82
  - lib/airwallex/middleware/idempotency.rb
83
+ - lib/airwallex/resources/balance.rb
84
+ - lib/airwallex/resources/batch_transfer.rb
92
85
  - lib/airwallex/resources/beneficiary.rb
86
+ - lib/airwallex/resources/conversion.rb
87
+ - lib/airwallex/resources/customer.rb
88
+ - lib/airwallex/resources/dispute.rb
93
89
  - lib/airwallex/resources/payment_intent.rb
90
+ - lib/airwallex/resources/payment_method.rb
91
+ - lib/airwallex/resources/quote.rb
92
+ - lib/airwallex/resources/rate.rb
93
+ - lib/airwallex/resources/refund.rb
94
94
  - lib/airwallex/resources/transfer.rb
95
95
  - lib/airwallex/util.rb
96
96
  - lib/airwallex/version.rb
@@ -1,130 +0,0 @@
1
- # Iteration 1 - Quick Start
2
-
3
- **Sprint 1, Iteration 1**
4
- **Date:** 25 November 2025
5
- **Status:** ✅ Complete
6
-
7
- ## What Was Built
8
-
9
- Core infrastructure for the Airwallex Ruby gem including:
10
-
11
- 1. ✅ Complete directory structure
12
- 2. ✅ Configuration management (sandbox/production)
13
- 3. ✅ HTTP client with Faraday
14
- 4. ✅ Bearer token authentication with auto-refresh
15
- 5. ✅ Comprehensive error handling
16
- 6. ✅ Automatic idempotency
17
- 7. ✅ Webhook signature verification
18
- 8. ✅ Utility helpers
19
- 9. ✅ Zero Rubocop offenses
20
-
21
- ## Quick Test
22
-
23
- ```ruby
24
- require 'airwallex'
25
-
26
- # Configure the gem
27
- Airwallex.configure do |config|
28
- config.api_key = 'your_api_key'
29
- config.client_id = 'your_client_id'
30
- config.environment = :sandbox
31
- end
32
-
33
- # Check configuration
34
- puts Airwallex.configuration.api_url
35
- # => https://api-demo.airwallex.com/api/v1
36
-
37
- # Client is ready (authentication happens automatically on first request)
38
- client = Airwallex.client
39
- ```
40
-
41
- ## Files Created
42
-
43
- ```text
44
- lib/airwallex.rb - Main module with configuration
45
- lib/airwallex/version.rb - Version constant
46
- lib/airwallex/configuration.rb - Configuration class
47
- lib/airwallex/client.rb - HTTP client with Faraday
48
- lib/airwallex/errors.rb - Exception hierarchy
49
- lib/airwallex/util.rb - Helper utilities
50
- lib/airwallex/webhook.rb - Webhook verification
51
- lib/airwallex/middleware/idempotency.rb - Auto request_id
52
- lib/airwallex/middleware/auth_refresh.rb - Token management
53
- ```
54
-
55
- ## Key Features
56
-
57
- ### Environment Safety
58
- - Defaults to `:sandbox` to prevent accidental production transactions
59
- - Validates environment selection
60
- - Dynamic URL generation
61
-
62
- ### Authentication
63
- - Automatic Bearer token exchange
64
- - 30-minute token lifetime with 5-minute refresh buffer
65
- - Thread-safe token management
66
- - Transparent 401 retry
67
-
68
- ### Idempotency
69
- - Automatic UUID v4 generation for `request_id`
70
- - Injected into request body (Airwallex specification)
71
- - Safe request retries
72
-
73
- ### Error Handling
74
- - HTTP status mapped to specific exceptions
75
- - Polymorphic error body parsing
76
- - Detailed error information (`code`, `message`, `param`, `details`)
77
-
78
- ### Webhook Security
79
- - HMAC-SHA256 signature verification
80
- - Replay attack protection (5-minute tolerance)
81
- - Constant-time comparison
82
-
83
- ## What's Next
84
-
85
- **Iteration 2:** Testing Infrastructure
86
- - Set up RSpec, WebMock, VCR
87
- - Write comprehensive tests
88
- - Achieve 90%+ coverage
89
-
90
- **Sprint 2:** Resource Implementation
91
- - APIResource base class
92
- - Payment Intent resource
93
- - Transfer resource
94
- - Pagination system
95
-
96
- ## Validation
97
-
98
- ```bash
99
- # Check for errors
100
- bundle exec rubocop lib/
101
- # => 9 files inspected, no offenses detected ✅
102
-
103
- # Test gem loading
104
- bundle exec ruby -e "require './lib/airwallex'; puts 'OK'"
105
- # => OK ✅
106
-
107
- # Test configuration
108
- bundle exec ruby -e "
109
- require './lib/airwallex'
110
- Airwallex.configure { |c| c.api_key = 'test'; c.client_id = 'test' }
111
- puts Airwallex.configuration.api_url
112
- "
113
- # => https://api-demo.airwallex.com/api/v1 ✅
114
- ```
115
-
116
- ## Time Spent
117
-
118
- Approximately 4 hours for:
119
- - Directory structure setup
120
- - Core class implementation
121
- - Faraday middleware
122
- - Rubocop configuration
123
- - Documentation
124
-
125
- ## Notes
126
-
127
- - All code follows Ruby 3.1+ standards
128
- - No external API calls made yet (no tests)
129
- - Architecture matches research blueprints exactly
130
- - Ready for test implementation
@@ -1,342 +0,0 @@
1
- # Sprint 1 - Iteration 1 Summary
2
-
3
- **Date:** 25 November 2025
4
- **Status:** Core Architecture Complete
5
- **Rubocop:** ✅ No offenses
6
-
7
- ## Completed Work
8
-
9
- ### 1. Gem Directory Structure ✅
10
-
11
- Created the complete directory structure as per architectural blueprint:
12
-
13
- ```text
14
- lib/airwallex/
15
- ├── api_operations/ (ready for Sprint 2)
16
- ├── resources/ (ready for Sprint 2)
17
- ├── middleware/
18
- │ ├── idempotency.rb
19
- │ └── auth_refresh.rb
20
- ├── errors.rb
21
- ├── client.rb
22
- ├── configuration.rb
23
- ├── util.rb
24
- ├── webhook.rb
25
- └── version.rb
26
- ```
27
-
28
- **Files Created:**
29
- - 9 Ruby files
30
- - 3 directories
31
- - All files pass Rubocop validation
32
-
33
- ### 2. Configuration Management ✅
34
-
35
- Implemented `Airwallex::Configuration` class with:
36
-
37
- **Features:**
38
- - Environment selection (`:sandbox`, `:production`)
39
- - Dynamic URL generation for API and Files endpoints
40
- - Safe defaults (sandbox as default environment)
41
- - Credential validation with detailed error messages
42
- - API version pinning (default: `2024-09-27`)
43
- - Logger configuration support
44
-
45
- **Key Methods:**
46
- - `#api_url` - Returns environment-specific API base URL
47
- - `#files_url` - Returns environment-specific files URL
48
- - `#validate!` - Validates configuration before use
49
- - `#configured?` - Checks if minimum configuration is present
50
- - `#environment=` - Validates and sets environment with type checking
51
-
52
- **URLs:**
53
- - Sandbox API: `https://api-demo.airwallex.com/api/v1`
54
- - Production API: `https://api.airwallex.com/api/v1`
55
- - Sandbox Files: `https://files-demo.airwallex.com`
56
- - Production Files: `https://files.airwallex.com`
57
-
58
- ### 3. HTTP Transport Layer ✅
59
-
60
- Implemented `Airwallex::Client` class using Faraday with:
61
-
62
- **Middleware Stack:**
63
- 1. JSON request encoding
64
- 2. Multipart support (for file uploads)
65
- 3. Retry logic with exponential backoff
66
- 4. JSON response parsing
67
- 5. Logging (when logger configured)
68
-
69
- **Headers Automatically Injected:**
70
- - `Content-Type: application/json`
71
- - `User-Agent: Airwallex-Ruby/{version} Ruby/{ruby_version}`
72
- - `x-api-version: 2024-09-27`
73
- - `Authorization: Bearer {token}` (after authentication)
74
-
75
- **Retry Configuration:**
76
- - Max retries: 3
77
- - Initial interval: 0.5s
78
- - Backoff factor: 2x
79
- - Jitter: 50% randomness
80
- - Safe methods: GET, DELETE
81
- - Retry statuses: 429, 500, 502, 503, 504
82
-
83
- **HTTP Methods:**
84
- - `#get(path, params, headers)`
85
- - `#post(path, body, headers)`
86
- - `#put(path, body, headers)`
87
- - `#patch(path, body, headers)`
88
- - `#delete(path, params, headers)`
89
-
90
- ### 4. Authentication System ✅
91
-
92
- Implemented Bearer token authentication with:
93
-
94
- **Token Exchange:**
95
- - Endpoint: `POST /api/v1/authentication/login`
96
- - Headers: `x-client-id`, `x-api-key`
97
- - Response: JWT token with 30-minute expiration
98
-
99
- **Token Lifecycle:**
100
- - Automatic tracking of token expiration
101
- - Proactive refresh (5-minute buffer before expiration)
102
- - Thread-safe token management (Mutex protection)
103
- - Transparent re-authentication on 401 errors
104
-
105
- **Key Methods:**
106
- - `#authenticate!` - Exchanges credentials for access token
107
- - `#token_expired?` - Checks if token needs refresh
108
- - `#ensure_authenticated!` - Ensures valid token before request
109
-
110
- ### 5. Token Refresh Middleware ✅
111
-
112
- Implemented `Airwallex::Middleware::AuthRefresh` with:
113
-
114
- **Features:**
115
- - Automatic token validation before each request
116
- - Intercepts 401 Unauthorized responses
117
- - One automatic retry after token refresh
118
- - Skips authentication for login endpoint itself
119
- - Prevents infinite retry loops
120
-
121
- **Behavior:**
122
- 1. Check token validity before request
123
- 2. Refresh if expired (proactive)
124
- 3. If 401 response, refresh and retry once
125
- 4. Update Authorization header with new token
126
-
127
- ### 6. Error Handling Framework ✅
128
-
129
- Implemented comprehensive error system with:
130
-
131
- **Exception Hierarchy:**
132
- ```ruby
133
- Airwallex::Error (base)
134
- ├── Airwallex::ConfigurationError
135
- ├── Airwallex::BadRequestError (400)
136
- ├── Airwallex::AuthenticationError (401)
137
- ├── Airwallex::PermissionError (403)
138
- │ └── Airwallex::SCARequiredError
139
- ├── Airwallex::NotFoundError (404)
140
- ├── Airwallex::RateLimitError (429)
141
- ├── Airwallex::APIError (500+)
142
- ├── Airwallex::InsufficientFundsError
143
- └── Airwallex::SignatureVerificationError
144
- ```
145
-
146
- **Error Attributes:**
147
- - `code` - API error code (e.g., `insufficient_fund`)
148
- - `message` - Human-readable error message
149
- - `param` - Field that caused the error (from `source`)
150
- - `details` - Additional error details array
151
- - `http_status` - HTTP status code
152
-
153
- **Polymorphic Error Parsing:**
154
- Handles all three API error formats:
155
- 1. Simple: `{ code, message, source }`
156
- 2. Complex: `{ code, message, details[] }`
157
- 3. Nested: `{ code, message, source: "nested.path" }`
158
-
159
- ### 7. Idempotency Middleware ✅
160
-
161
- Implemented `Airwallex::Middleware::Idempotency` with:
162
-
163
- **Features:**
164
- - Automatic UUID v4 generation for `request_id`
165
- - Injection into request body (not headers, as per Airwallex spec)
166
- - Preserves user-provided `request_id` for reconciliation
167
- - Only applies to POST, PUT, PATCH methods
168
-
169
- **Behavior:**
170
- ```ruby
171
- # Without request_id
172
- { amount: 100 } → { amount: 100, request_id: "uuid-v4" }
173
-
174
- # With request_id (preserved)
175
- { amount: 100, request_id: "my-id" } → { amount: 100, request_id: "my-id" }
176
- ```
177
-
178
- **Safety:**
179
- - Prevents duplicate financial transactions
180
- - Enables safe request retries
181
- - Supports reconciliation with internal systems
182
-
183
- ### 8. Utility Modules ✅
184
-
185
- Implemented `Airwallex::Util` with helpers for:
186
-
187
- **Date/Time Formatting:**
188
- - `#format_date_time` - Converts Ruby Date/Time to ISO 8601
189
- - `#parse_date_time` - Parses ISO 8601 strings to Time objects
190
- - Automatic timezone conversion to UTC
191
-
192
- **Data Manipulation:**
193
- - `#symbolize_keys` - Convert hash keys to symbols
194
- - `#deep_symbolize_keys` - Recursive symbol conversion
195
- - `#to_money` - Convert values to BigDecimal for precision
196
-
197
- **Idempotency:**
198
- - `#generate_idempotency_key` - Creates UUID v4 for request_id
199
-
200
- ### 9. Webhook Verification ✅
201
-
202
- Implemented `Airwallex::Webhook` module with:
203
-
204
- **Signature Verification:**
205
- - Algorithm: HMAC-SHA256
206
- - Data: `timestamp + payload`
207
- - Constant-time comparison (timing attack prevention)
208
-
209
- **Replay Protection:**
210
- - Timestamp tolerance: 300 seconds (5 minutes, configurable)
211
- - Rejects old/replayed webhooks
212
-
213
- **Event Construction:**
214
- - `Event` object with `id`, `type`, `data`, `created_at`
215
- - JSON parsing with error handling
216
- - Immutable event objects
217
-
218
- **Key Methods:**
219
- - `#construct_event` - Full verification and parsing
220
- - `#verify_signature` - HMAC validation
221
- - `#compute_signature` - Signature generation
222
- - `#verify_timestamp` - Replay protection
223
-
224
- ### 10. Main Module Integration ✅
225
-
226
- Updated `lib/airwallex.rb` with:
227
-
228
- **Configuration Block:**
229
- ```ruby
230
- Airwallex.configure do |config|
231
- config.api_key = "key"
232
- config.client_id = "id"
233
- config.environment = :sandbox
234
- end
235
- ```
236
-
237
- **Module Methods:**
238
- - `Airwallex.configuration` - Access current configuration
239
- - `Airwallex.configure { }` - Configure the gem
240
- - `Airwallex.client` - Get singleton client instance
241
- - `Airwallex.reset!` - Reset configuration and client
242
-
243
- ### 11. Code Quality ✅
244
-
245
- **Rubocop Configuration:**
246
- - Disabled overly strict cops for financial software
247
- - Increased method length limits (15 lines)
248
- - Increased ABC size limits (25)
249
- - Disabled documentation requirement (will add later)
250
- - Allowed short parameter names (a, b for comparison)
251
-
252
- **Current Status:**
253
- - ✅ 9 files inspected
254
- - ✅ 0 offenses detected
255
- - ✅ All code follows style guide
256
- - ✅ No syntax errors
257
- - ✅ All requires working
258
-
259
- ## Technical Decisions
260
-
261
- ### 1. Idempotency in Body vs Header
262
-
263
- **Decision:** Inject `request_id` in request body
264
- **Rationale:** Airwallex API specification requires it in body, unlike Stripe which uses headers
265
- **Impact:** Custom middleware required instead of standard Faraday middleware
266
-
267
- ### 2. Sandbox as Default Environment
268
-
269
- **Decision:** Default to `:sandbox` environment
270
- **Rationale:** Safety - prevents accidental real-money transactions
271
- **Impact:** Users must explicitly opt-in to production
272
-
273
- ### 3. Token Refresh Buffer
274
-
275
- **Decision:** Refresh tokens 5 minutes before expiration
276
- **Rationale:** Prevents mid-request expiration in long-running operations
277
- **Impact:** Slightly more authentication calls, but better reliability
278
-
279
- ### 4. Thread-Safe Token Management
280
-
281
- **Decision:** Use Mutex for token refresh
282
- **Rationale:** Prevent race conditions in multi-threaded environments
283
- **Impact:** Small performance cost, but ensures correctness
284
-
285
- ### 5. Retry Logic Separation
286
-
287
- **Decision:** Separate retry logic in Faraday middleware, not in idempotency middleware
288
- **Rationale:** Clear separation of concerns, easier to test
289
- **Impact:** Two middleware components instead of one
290
-
291
- ## Known Limitations
292
-
293
- 1. **No Resource Classes Yet** - Only infrastructure, no domain objects (Sprint 2)
294
- 2. **No Pagination** - AutoPaginator not implemented (Sprint 2)
295
- 3. **No OAuth Support** - Only Bearer token (Sprint 2+)
296
- 4. **No SCA Handling** - Strong Customer Authentication deferred (Sprint 2+)
297
- 5. **No Tests Yet** - Test infrastructure is next task
298
-
299
- ## Next Steps
300
-
301
- ### Immediate (Iteration 2)
302
-
303
- 1. Set up RSpec, WebMock, VCR
304
- 2. Create test fixtures for API responses
305
- 3. Write tests for Configuration class
306
- 4. Write tests for Client authentication flow
307
- 5. Write tests for Error parsing (all formats)
308
- 6. Write tests for Idempotency middleware
309
- 7. Write tests for AuthRefresh middleware
310
- 8. Achieve 90%+ code coverage
311
-
312
- ### Sprint 2 Preview
313
-
314
- 1. Implement APIResource base class
315
- 2. Create API operations mixins (Create, Retrieve, List)
316
- 3. Build pagination system (cursor and offset)
317
- 4. Implement first resources:
318
- - PaymentIntent
319
- - Transfer
320
- - Beneficiary
321
- 5. Add OAuth support for Platform accounts
322
-
323
- ## Metrics
324
-
325
- - **Files Created:** 9
326
- - **Lines of Code:** ~500
327
- - **Rubocop Offenses:** 0
328
- - **Test Coverage:** 0% (tests next iteration)
329
- - **Dependencies Added:** 3 runtime, 3 development
330
- - **Time Spent:** ~4 hours
331
-
332
- ## Risks Mitigated
333
-
334
- 1. ✅ Token refresh race conditions - Mutex locks implemented
335
- 2. ✅ Request duplication - Automatic idempotency keys
336
- 3. ✅ Accidental production usage - Sandbox default
337
- 4. ✅ Webhook replay attacks - Timestamp validation
338
- 5. ✅ Timing attacks on signatures - Constant-time comparison
339
-
340
- ## Open Questions
341
-
342
- None - architecture is solid and ready for testing.