airwallex 0.1.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 (35) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +377 -0
  5. data/Rakefile +12 -0
  6. data/docs/internal/20251125_iteration_1_quickstart.md +130 -0
  7. data/docs/internal/20251125_iteration_1_summary.md +342 -0
  8. data/docs/internal/20251125_sprint_1_completed.md +448 -0
  9. data/docs/internal/20251125_sprint_1_plan.md +389 -0
  10. data/docs/internal/20251125_sprint_2_completed.md +559 -0
  11. data/docs/internal/20251125_sprint_2_plan.md +531 -0
  12. data/docs/internal/20251125_sprint_2_unit_tests_completed.md +264 -0
  13. data/docs/research/Airwallex API Endpoint Research.md +410 -0
  14. data/docs/research/Airwallex API Research for Ruby Gem.md +383 -0
  15. data/lib/airwallex/api_operations/create.rb +16 -0
  16. data/lib/airwallex/api_operations/delete.rb +16 -0
  17. data/lib/airwallex/api_operations/list.rb +23 -0
  18. data/lib/airwallex/api_operations/retrieve.rb +16 -0
  19. data/lib/airwallex/api_operations/update.rb +44 -0
  20. data/lib/airwallex/api_resource.rb +96 -0
  21. data/lib/airwallex/client.rb +132 -0
  22. data/lib/airwallex/configuration.rb +67 -0
  23. data/lib/airwallex/errors.rb +64 -0
  24. data/lib/airwallex/list_object.rb +85 -0
  25. data/lib/airwallex/middleware/auth_refresh.rb +32 -0
  26. data/lib/airwallex/middleware/idempotency.rb +29 -0
  27. data/lib/airwallex/resources/beneficiary.rb +14 -0
  28. data/lib/airwallex/resources/payment_intent.rb +44 -0
  29. data/lib/airwallex/resources/transfer.rb +23 -0
  30. data/lib/airwallex/util.rb +58 -0
  31. data/lib/airwallex/version.rb +5 -0
  32. data/lib/airwallex/webhook.rb +67 -0
  33. data/lib/airwallex.rb +49 -0
  34. data/sig/airwallex.rbs +4 -0
  35. metadata +128 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7517d087f8337d63e00476a7be3e551164e97bd271c510ac37621db595a4c86f
4
+ data.tar.gz: 7b5a90c9a5a9ca677f48ce63d9a1bb50a9cedd030a2f13ae39c658b2fe8d63e9
5
+ SHA512:
6
+ metadata.gz: 9a6344fa43f9f38ae9950cf8c84a0770361f5448bcb365d6e44d20068986f831e23d628c41f07402d94d3c0bb71306095fd2fd46eca867a61abfb79f18b57fd9
7
+ data.tar.gz: '08d8be5e80b739ce7278af4f894744fa708d9aa755fda96b415a881c24218692c4336602b1c59c765d9ea0dd3a705f3cdc6e0b42108979d6ad6ce097f5b46e83'
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-11-25
4
+
5
+ ### Added
6
+ - Core infrastructure: Configuration, Client, Error handling
7
+ - Authentication: Bearer token with automatic refresh
8
+ - API Resources:
9
+ - PaymentIntent (create, retrieve, list, update, confirm, cancel, capture)
10
+ - Transfer (create, retrieve, list, cancel)
11
+ - Beneficiary (create, retrieve, list, delete)
12
+ - API Operations: Reusable mixins for Create, Retrieve, List, Update, Delete
13
+ - Pagination: Unified ListObject with auto-paging support (cursor and offset-based)
14
+ - Idempotency: Automatic request_id generation for safe retries
15
+ - Webhook verification: HMAC-SHA256 signature validation
16
+ - Multi-environment support: Sandbox and Production
17
+ - Comprehensive test suite: 187 tests with 100% coverage
18
+ - Ruby 3.1+ support
19
+
20
+ ### Notes
21
+ - This is an MVP release focusing on core payment acceptance and payout functionality
22
+ - Additional resources (FX, cards, refunds) will be added in future versions
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Chayut Orapinpatipat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,377 @@
1
+ # Airwallex Ruby Gem
2
+
3
+ A Ruby client library for the [Airwallex API](https://www.airwallex.com/docs/api), providing access to payment acceptance and payout capabilities.
4
+
5
+ ## Overview
6
+
7
+ This gem provides a Ruby interface to Airwallex's payment infrastructure, designed for Ruby 3.1+ applications. It includes core functionality for authentication management, idempotency guarantees, webhook verification, and multi-environment support.
8
+
9
+ **Current Features (v0.1.0):**
10
+
11
+ - **Authentication**: Bearer token authentication with automatic refresh
12
+ - **Payment Acceptance**: Payment intent creation, confirmation, and management
13
+ - **Payouts**: Transfer creation and beneficiary management
14
+ - **Idempotency**: Automatic request deduplication for safe retries
15
+ - **Pagination**: Unified interface over cursor-based and offset-based pagination
16
+ - **Webhook Security**: HMAC-SHA256 signature verification with replay protection
17
+ - **Sandbox Support**: Full testing environment for development
18
+
19
+ **Note:** This is an initial MVP release. Additional resources (FX, cards, refunds, etc.) will be added in future versions.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'airwallex'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ ```bash
32
+ bundle install
33
+ ```
34
+
35
+ Or install it yourself as:
36
+
37
+ ```bash
38
+ gem install airwallex
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Configuration
44
+
45
+ ```ruby
46
+ require 'airwallex'
47
+
48
+ Airwallex.configure do |config|
49
+ config.api_key = 'your_api_key'
50
+ config.client_id = 'your_client_id'
51
+ config.environment = :sandbox # or :production
52
+ end
53
+ ```
54
+
55
+ ### Creating a Payment Intent
56
+
57
+ ```ruby
58
+ # Create a payment intent
59
+ payment_intent = Airwallex::PaymentIntent.create(
60
+ amount: 100.00,
61
+ currency: 'USD',
62
+ merchant_order_id: 'order_123',
63
+ return_url: 'https://yoursite.com/return'
64
+ )
65
+
66
+ # Confirm with card details
67
+ payment_intent.confirm(
68
+ payment_method: {
69
+ type: 'card',
70
+ card: {
71
+ number: '4242424242424242',
72
+ expiry_month: '12',
73
+ expiry_year: '2025',
74
+ cvc: '123'
75
+ }
76
+ }
77
+ )
78
+ ```
79
+
80
+ ### Creating a Payout
81
+
82
+ ```ruby
83
+ # Create a beneficiary
84
+ beneficiary = Airwallex::Beneficiary.create(
85
+ bank_details: {
86
+ account_number: '123456789',
87
+ account_routing_type1: 'aba',
88
+ account_routing_value1: '026009593',
89
+ bank_country_code: 'US'
90
+ },
91
+ beneficiary_type: 'BUSINESS',
92
+ company_name: 'Acme Corp'
93
+ )
94
+
95
+ # Execute transfer
96
+ transfer = Airwallex::Transfer.create(
97
+ beneficiary_id: beneficiary.id,
98
+ source_currency: 'USD',
99
+ transfer_method: 'LOCAL',
100
+ amount: 1000.00,
101
+ reason: 'Payment for services'
102
+ )
103
+ ```
104
+
105
+ ## Usage
106
+
107
+ ### Authentication
108
+
109
+ The gem uses Bearer token authentication with automatic token refresh:
110
+
111
+ ```ruby
112
+ Airwallex.configure do |config|
113
+ config.api_key = 'your_api_key'
114
+ config.client_id = 'your_client_id'
115
+ config.environment = :sandbox # or :production
116
+ end
117
+ ```
118
+
119
+ Tokens are automatically refreshed when they expire, and the gem handles thread-safe token management.
120
+
121
+ ### Idempotency
122
+
123
+ The gem automatically handles idempotency for safe retries:
124
+
125
+ ```ruby
126
+ # Automatic request_id generation
127
+ transfer = Airwallex::Transfer.create(
128
+ amount: 500.00,
129
+ beneficiary_id: 'ben_123'
130
+ # request_id automatically generated
131
+ )
132
+
133
+ # Or provide your own for reconciliation
134
+ transfer = Airwallex::Transfer.create(
135
+ amount: 500.00,
136
+ beneficiary_id: 'ben_123',
137
+ request_id: 'my_internal_id_789'
138
+ )
139
+ ```
140
+
141
+ ### Pagination
142
+
143
+ Unified interface across both cursor-based and offset-based endpoints:
144
+
145
+ ```ruby
146
+ # Auto-pagination with enumerable
147
+ Airwallex::Transfer.list.auto_paging_each do |transfer|
148
+ puts transfer.id
149
+ end
150
+
151
+ # Manual pagination
152
+ transfers = Airwallex::Transfer.list(page_size: 50)
153
+ while transfers.has_more?
154
+ transfers.each { |t| process(t) }
155
+ transfers = transfers.next_page
156
+ end
157
+ ```
158
+
159
+ ### Webhook Handling
160
+
161
+ ```ruby
162
+ # In your webhook controller
163
+ payload = request.body.read
164
+ signature = request.headers['x-signature']
165
+ timestamp = request.headers['x-timestamp']
166
+
167
+ begin
168
+ event = Airwallex::Webhook.construct_event(
169
+ payload,
170
+ signature,
171
+ timestamp,
172
+ tolerance: 300 # 5 minutes
173
+ )
174
+
175
+ case event.type
176
+ when 'payment_intent.succeeded'
177
+ handle_successful_payment(event.data)
178
+ when 'payout.transfer.failed'
179
+ handle_failed_payout(event.data)
180
+ end
181
+ rescue Airwallex::SignatureVerificationError => e
182
+ # Invalid signature
183
+ head :bad_request
184
+ end
185
+ ```
186
+
187
+ ### Error Handling
188
+
189
+ ```ruby
190
+ begin
191
+ transfer = Airwallex::Transfer.create(params)
192
+ rescue Airwallex::InsufficientFundsError => e
193
+ # Handle insufficient balance
194
+ notify_user("Insufficient funds: #{e.message}")
195
+ rescue Airwallex::RateLimitError => e
196
+ # Rate limit hit - automatic retry with backoff
197
+ retry_with_backoff
198
+ rescue Airwallex::AuthenticationError => e
199
+ # Invalid credentials
200
+ log_error("Auth failed: #{e.message}")
201
+ rescue Airwallex::APIError => e
202
+ # General API error
203
+ log_error("API error: #{e.code} - #{e.message}")
204
+ end
205
+ ```
206
+
207
+ ## Architecture
208
+
209
+ ### Design Principles
210
+
211
+ - **Correctness First**: Automatic idempotency and type safety prevent duplicate transactions
212
+ - **Fail-Safe Defaults**: Sandbox environment default, automatic token refresh
213
+ - **Developer Experience**: Auto-pagination, dynamic schema validation, structured errors
214
+ - **Security**: HMAC webhook verification, constant-time signature comparison, SCA support
215
+ - **Resilience**: Exponential backoff, jittered retries, concurrent request limits
216
+
217
+ ### Core Components
218
+
219
+ ```
220
+ lib/airwallex/
221
+ ├── api_operations/ # CRUD operation mixins (Create, Retrieve, List, Update, Delete)
222
+ ├── resources/ # Implemented resources
223
+ │ ├── payment_intent.rb # Payment acceptance
224
+ │ ├── transfer.rb # Payouts
225
+ │ └── beneficiary.rb # Payout beneficiaries
226
+ ├── api_resource.rb # Base resource class with dynamic attributes
227
+ ├── list_object.rb # Pagination wrapper
228
+ ├── errors.rb # Exception hierarchy
229
+ ├── client.rb # HTTP client with authentication
230
+ ├── configuration.rb # Environment and credentials
231
+ ├── webhook.rb # Signature verification
232
+ ├── util.rb # Helper methods
233
+ └── middleware/ # Faraday middleware
234
+ └── idempotency.rb # Automatic request_id injection
235
+ ```
236
+
237
+ ## Development
238
+
239
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
240
+
241
+ ### Running Tests
242
+
243
+ ```bash
244
+ bundle exec rspec
245
+ ```
246
+
247
+ ### Code Style
248
+
249
+ ```bash
250
+ bundle exec rubocop
251
+ ```
252
+
253
+ ### Local Development
254
+
255
+ ```ruby
256
+ # In bin/console or irb
257
+ require 'airwallex'
258
+
259
+ Airwallex.configure do |config|
260
+ config.environment = :sandbox
261
+ config.api_key = ENV['AIRWALLEX_API_KEY']
262
+ config.client_id = ENV['AIRWALLEX_CLIENT_ID']
263
+ end
264
+ ```
265
+
266
+ ## API Coverage (v0.1.0)
267
+
268
+ ### Currently Implemented Resources
269
+
270
+ - **Payment Acceptance**: PaymentIntent (create, retrieve, list, update, confirm, cancel, capture)
271
+ - **Payouts**: Transfer (create, retrieve, list, cancel), Beneficiary (create, retrieve, list, delete)
272
+ - **Webhooks**: Event handling, HMAC-SHA256 signature verification
273
+
274
+ ### Coming in Future Versions
275
+
276
+ - Refunds and disputes
277
+ - Foreign exchange (rates, quotes, conversions)
278
+ - Payment methods management
279
+ - Global accounts
280
+ - Card issuing
281
+ - Additional payout methods
282
+
283
+ ## Environment Support
284
+
285
+ ### Sandbox
286
+
287
+ Testing environment for development:
288
+
289
+ ```ruby
290
+ Airwallex.configure do |config|
291
+ config.environment = :sandbox
292
+ config.api_key = ENV['AIRWALLEX_SANDBOX_API_KEY']
293
+ config.client_id = ENV['AIRWALLEX_SANDBOX_CLIENT_ID']
294
+ end
295
+ ```
296
+
297
+ ### Production
298
+
299
+ Live environment for real financial transactions:
300
+
301
+ ```ruby
302
+ Airwallex.configure do |config|
303
+ config.environment = :production
304
+ config.api_key = ENV['AIRWALLEX_API_KEY']
305
+ config.client_id = ENV['AIRWALLEX_CLIENT_ID']
306
+ end
307
+ ```
308
+
309
+ ## Rate Limits
310
+
311
+ The gem respects Airwallex API rate limits. If you encounter `Airwallex::RateLimitError`, implement retry logic with exponential backoff:
312
+
313
+ ```ruby
314
+ begin
315
+ transfer = Airwallex::Transfer.create(params)
316
+ rescue Airwallex::RateLimitError => e
317
+ sleep(2 ** retry_count)
318
+ retry_count += 1
319
+ retry if retry_count < 3
320
+ end
321
+ ```
322
+
323
+ ## Contributing
324
+
325
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/Sentia/airwallex>.
326
+
327
+ ### Development Setup
328
+
329
+ 1. Fork and clone the repository
330
+ 2. Run `bin/setup` to install dependencies
331
+ 3. Create a `.env` file with sandbox credentials
332
+ 4. Run tests: `bundle exec rspec`
333
+ 5. Check style: `bundle exec rubocop`
334
+
335
+ ### Guidelines
336
+
337
+ - Write tests for new features
338
+ - Follow existing code style (enforced by Rubocop)
339
+ - Update documentation for API changes
340
+ - Ensure all tests pass before submitting PR
341
+
342
+ ## Versioning
343
+
344
+ This gem follows [Semantic Versioning](https://semver.org/). The Airwallex API uses date-based versioning, which is handled internally by the gem.
345
+
346
+ ## Security
347
+
348
+ If you discover a security vulnerability, please email security@sentia.com instead of using the issue tracker.
349
+
350
+ ## Documentation
351
+
352
+ - [Airwallex API Documentation](https://www.airwallex.com/docs/api)
353
+ - [API Reference](https://www.airwallex.com/docs/api#overview)
354
+
355
+ ## Requirements
356
+
357
+ - Ruby 3.1 or higher
358
+ - Bundler 2.0 or higher
359
+
360
+ ## Dependencies
361
+
362
+ - `faraday` (~> 2.0) - HTTP client
363
+ - `faraday-retry` - Request retry logic
364
+ - `faraday-multipart` - File upload support
365
+
366
+ ## License
367
+
368
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
369
+
370
+ ## Support
371
+
372
+ - GitHub Issues: <https://github.com/Sentia/airwallex/issues>
373
+ - Airwallex Support: <https://www.airwallex.com/support>
374
+
375
+ ## Acknowledgments
376
+
377
+ Built with comprehensive analysis of the Airwallex API ecosystem. Special thanks to the Airwallex team for their extensive documentation and developer resources.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,130 @@
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