airwallex 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +166 -7
- data/lib/airwallex/api_operations/list.rb +27 -8
- data/lib/airwallex/errors.rb +1 -0
- data/lib/airwallex/resources/balance.rb +59 -0
- data/lib/airwallex/resources/batch_transfer.rb +32 -0
- data/lib/airwallex/resources/conversion.rb +43 -0
- data/lib/airwallex/resources/customer.rb +40 -0
- data/lib/airwallex/resources/dispute.rb +68 -0
- data/lib/airwallex/resources/payment_method.rb +51 -0
- data/lib/airwallex/resources/quote.rb +51 -0
- data/lib/airwallex/resources/rate.rb +33 -0
- data/lib/airwallex/resources/refund.rb +34 -0
- data/lib/airwallex/version.rb +1 -1
- data/lib/airwallex.rb +9 -0
- metadata +16 -15
- data/docs/internal/20251125_iteration_1_quickstart.md +0 -130
- data/docs/internal/20251125_iteration_1_summary.md +0 -342
- data/docs/internal/20251125_sprint_1_completed.md +0 -448
- data/docs/internal/20251125_sprint_1_plan.md +0 -389
- data/docs/internal/20251125_sprint_2_completed.md +0 -559
- data/docs/internal/20251125_sprint_2_plan.md +0 -531
- data/docs/internal/20251125_sprint_2_unit_tests_completed.md +0 -264
- data/docs/research/Airwallex API Endpoint Research.md +0 -410
- data/docs/research/Airwallex API Research for Ruby Gem.md +0 -383
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a refund of a payment intent
|
|
5
|
+
#
|
|
6
|
+
# Refunds can be full or partial. Multiple refunds can be created for a single
|
|
7
|
+
# payment intent as long as the total refunded amount doesn't exceed the original amount.
|
|
8
|
+
#
|
|
9
|
+
# @example Create a full refund
|
|
10
|
+
# refund = Airwallex::Refund.create(
|
|
11
|
+
# payment_intent_id: "pi_123",
|
|
12
|
+
# amount: 100.00,
|
|
13
|
+
# reason: "requested_by_customer"
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example Create a partial refund
|
|
17
|
+
# refund = Airwallex::Refund.create(
|
|
18
|
+
# payment_intent_id: "pi_123",
|
|
19
|
+
# amount: 25.00
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @example List refunds for a payment
|
|
23
|
+
# refunds = Airwallex::Refund.list(payment_intent_id: "pi_123")
|
|
24
|
+
class Refund < APIResource
|
|
25
|
+
extend APIOperations::Create
|
|
26
|
+
extend APIOperations::Retrieve
|
|
27
|
+
extend APIOperations::List
|
|
28
|
+
|
|
29
|
+
# @return [String] API resource path for refunds
|
|
30
|
+
def self.resource_path
|
|
31
|
+
"/api/v1/pa/refunds"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/airwallex/version.rb
CHANGED
data/lib/airwallex.rb
CHANGED
|
@@ -24,6 +24,15 @@ require_relative "airwallex/api_resource"
|
|
|
24
24
|
require_relative "airwallex/resources/payment_intent"
|
|
25
25
|
require_relative "airwallex/resources/transfer"
|
|
26
26
|
require_relative "airwallex/resources/beneficiary"
|
|
27
|
+
require_relative "airwallex/resources/refund"
|
|
28
|
+
require_relative "airwallex/resources/payment_method"
|
|
29
|
+
require_relative "airwallex/resources/customer"
|
|
30
|
+
require_relative "airwallex/resources/batch_transfer"
|
|
31
|
+
require_relative "airwallex/resources/dispute"
|
|
32
|
+
require_relative "airwallex/resources/rate"
|
|
33
|
+
require_relative "airwallex/resources/quote"
|
|
34
|
+
require_relative "airwallex/resources/conversion"
|
|
35
|
+
require_relative "airwallex/resources/balance"
|
|
27
36
|
|
|
28
37
|
module Airwallex
|
|
29
38
|
class << self
|
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.
|
|
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
|
|
57
|
-
|
|
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,15 +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/research/Airwallex API Endpoint Research.md
|
|
77
|
-
- docs/research/Airwallex API Research for Ruby Gem.md
|
|
78
70
|
- lib/airwallex.rb
|
|
79
71
|
- lib/airwallex/api_operations/create.rb
|
|
80
72
|
- lib/airwallex/api_operations/delete.rb
|
|
@@ -88,19 +80,28 @@ files:
|
|
|
88
80
|
- lib/airwallex/list_object.rb
|
|
89
81
|
- lib/airwallex/middleware/auth_refresh.rb
|
|
90
82
|
- lib/airwallex/middleware/idempotency.rb
|
|
83
|
+
- lib/airwallex/resources/balance.rb
|
|
84
|
+
- lib/airwallex/resources/batch_transfer.rb
|
|
91
85
|
- lib/airwallex/resources/beneficiary.rb
|
|
86
|
+
- lib/airwallex/resources/conversion.rb
|
|
87
|
+
- lib/airwallex/resources/customer.rb
|
|
88
|
+
- lib/airwallex/resources/dispute.rb
|
|
92
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
|
|
93
94
|
- lib/airwallex/resources/transfer.rb
|
|
94
95
|
- lib/airwallex/util.rb
|
|
95
96
|
- lib/airwallex/version.rb
|
|
96
97
|
- lib/airwallex/webhook.rb
|
|
97
98
|
- sig/airwallex.rbs
|
|
98
|
-
homepage: https://
|
|
99
|
+
homepage: https://www.sentia.com.au
|
|
99
100
|
licenses:
|
|
100
101
|
- MIT
|
|
101
102
|
metadata:
|
|
102
103
|
allowed_push_host: https://rubygems.org
|
|
103
|
-
homepage_uri: https://
|
|
104
|
+
homepage_uri: https://www.sentia.com.au
|
|
104
105
|
source_code_uri: https://github.com/Sentia/airwallex
|
|
105
106
|
changelog_uri: https://github.com/Sentia/airwallex/blob/main/CHANGELOG.md
|
|
106
107
|
documentation_uri: https://rubydoc.info/gems/airwallex
|
|
@@ -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.
|