airwallex-ruby 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bdf272d514644f46fb58ec553ed1f19350adc59aa8c979b50be19cd040754726
4
+ data.tar.gz: 6ed20800b0e6496f18d8b27a4cd7d7ad1dbb06fb462b987e3d88f3cf6aa0ee34
5
+ SHA512:
6
+ metadata.gz: 68d2e8a8d125438c26ec2a687f48a13fa0f2b0c02cfcbc8d4045db3007852d1ed7b477f402caddd170adf81841591a720fe0fcdde6c870e8ca020414acd47d3d
7
+ data.tar.gz: a636cc93cc28b718a814684d1544150ff30a7c69c808eb2228541f822ab8a36d3db1e058ce5111bc0ae040c1196e62e80a33c8bcb1a5f114af98a5c434c1558b
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /vendor/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
11
+ *.rbc
12
+ .DS_Store
13
+ .env
14
+ .env.*
15
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,67 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.1
4
+ Exclude:
5
+ - "vendor/**/*"
6
+ - "tmp/**/*"
7
+
8
+ Style/Documentation:
9
+ Enabled: false
10
+
11
+ Style/StringLiterals:
12
+ EnforcedStyle: double_quotes
13
+
14
+ Lint/EmptyClass:
15
+ Exclude:
16
+ - "lib/airwallex/client.rb"
17
+ - "lib/airwallex/resources/**/*"
18
+
19
+ Layout/LineLength:
20
+ Max: 120
21
+
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - "spec/**/*"
25
+ - "airwallex-ruby.gemspec"
26
+
27
+ Gemspec/DevelopmentDependencies:
28
+ Exclude:
29
+ - "airwallex-ruby.gemspec"
30
+
31
+ Style/FetchEnvVar:
32
+ Exclude:
33
+ - "lib/generators/**/*"
34
+ - "examples/**/*"
35
+
36
+ Metrics/ClassLength:
37
+ Exclude:
38
+ - "lib/airwallex/client.rb"
39
+
40
+ Metrics/AbcSize:
41
+ Exclude:
42
+ - "lib/airwallex/client.rb"
43
+ - "lib/airwallex/webhook.rb"
44
+
45
+ Metrics/MethodLength:
46
+ Exclude:
47
+ - "lib/airwallex/client.rb"
48
+
49
+ Metrics/ParameterLists:
50
+ Exclude:
51
+ - "lib/airwallex/client.rb"
52
+
53
+ Naming/PredicateMethod:
54
+ Exclude:
55
+ - "lib/airwallex/webhook.rb"
56
+
57
+ Naming/MethodParameterName:
58
+ Exclude:
59
+ - "lib/airwallex/webhook.rb"
60
+
61
+ Metrics/CyclomaticComplexity:
62
+ Exclude:
63
+ - "lib/airwallex/webhook.rb"
64
+
65
+ Metrics/PerceivedComplexity:
66
+ Exclude:
67
+ - "lib/airwallex/webhook.rb"
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - Unreleased
9
+
10
+ ### Added
11
+
12
+ - Configuration support
13
+ - Demo and production environments
14
+ - Faraday HTTP client
15
+ - Authentication and token caching
16
+ - PaymentIntents resource
17
+ - Refunds resource
18
+ - Idempotency key support
19
+ - Webhook signature verification
20
+ - Optional Rails initializer generator
21
+ - RSpec/WebMock test coverage
22
+ - Documentation and examples
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SenseiProject
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,433 @@
1
+ # airwallex-ruby
2
+
3
+ [![CI](https://github.com/delacruzjames/airwallex-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/delacruzjames/airwallex-ruby/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/github/license/delacruzjames/airwallex-ruby)](LICENSE.txt)
5
+
6
+ Unofficial Ruby SDK for Airwallex APIs.
7
+
8
+ ## Disclaimer
9
+
10
+ This is an unofficial Ruby SDK for Airwallex. It is not maintained, sponsored, or endorsed by Airwallex.
11
+
12
+ Requires **Ruby 3.1+**.
13
+
14
+ ## Features
15
+
16
+ - Configuration support
17
+ - Demo and production environments
18
+ - Authentication and token caching
19
+ - Faraday-based HTTP client
20
+ - Typed error handling
21
+ - PaymentIntents resource
22
+ - Refunds resource
23
+ - Idempotency key support
24
+ - Webhook signature verification
25
+ - Optional Rails initializer generator
26
+ - RSpec/WebMock test coverage
27
+
28
+ ## Installation
29
+
30
+ Add this line to your application's Gemfile:
31
+
32
+ ```ruby
33
+ gem "airwallex-ruby"
34
+ ```
35
+
36
+ Then run:
37
+
38
+ ```bash
39
+ bundle install
40
+ ```
41
+
42
+ For local development against a checkout of this repository:
43
+
44
+ ```ruby
45
+ gem "airwallex-ruby", path: "../airwallex-ruby"
46
+ ```
47
+
48
+ ## Basic configuration
49
+
50
+ ```ruby
51
+ require "airwallex"
52
+
53
+ Airwallex.configure do |config|
54
+ config.client_id = ENV["AIRWALLEX_CLIENT_ID"]
55
+ config.api_key = ENV["AIRWALLEX_API_KEY"]
56
+ config.login_as = ENV["AIRWALLEX_LOGIN_AS"]
57
+ config.environment = :demo
58
+ config.timeout = 30
59
+ config.open_timeout = 10
60
+ end
61
+
62
+ client = Airwallex.client
63
+ ```
64
+
65
+ ## Direct client initialization
66
+
67
+ You can also construct a client directly without global configuration:
68
+
69
+ ```ruby
70
+ client = Airwallex::Client.new(
71
+ client_id: ENV["AIRWALLEX_CLIENT_ID"],
72
+ api_key: ENV["AIRWALLEX_API_KEY"],
73
+ login_as: ENV["AIRWALLEX_LOGIN_AS"],
74
+ environment: :demo
75
+ )
76
+ ```
77
+
78
+ ## Environments
79
+
80
+ | Environment | Base URL |
81
+ |-------------|----------|
82
+ | `:demo` | `https://api-demo.airwallex.com/api/v1` |
83
+ | `:production` | `https://api.airwallex.com/api/v1` |
84
+
85
+ Set `config.environment` or pass `environment:` when creating a client.
86
+
87
+ ## Authentication
88
+
89
+ Authentication is handled automatically. When an authenticated request is made, the client calls `POST /authentication/login` if no valid token is cached. The access token is stored in memory and reused until it expires. All authenticated requests include `Authorization: Bearer <token>`.
90
+
91
+ You can authenticate explicitly or check authentication state:
92
+
93
+ ```ruby
94
+ client.authenticate
95
+ client.authenticated?
96
+ ```
97
+
98
+ ## PaymentIntents
99
+
100
+ ### Create
101
+
102
+ ```ruby
103
+ payment_intent = client.payment_intents.create(
104
+ {
105
+ amount: 1000,
106
+ currency: "PHP",
107
+ merchant_order_id: "ORDER-1001",
108
+ return_url: "https://example.com/return"
109
+ },
110
+ idempotency_key: "order-1001-create"
111
+ )
112
+
113
+ puts payment_intent["id"]
114
+ puts payment_intent["client_secret"]
115
+ ```
116
+
117
+ ### Retrieve
118
+
119
+ ```ruby
120
+ payment_intent = client.payment_intents.retrieve("int_123")
121
+ ```
122
+
123
+ ### Update
124
+
125
+ ```ruby
126
+ payment_intent = client.payment_intents.update(
127
+ "int_123",
128
+ {
129
+ amount: 1500
130
+ },
131
+ idempotency_key: "order-1001-update"
132
+ )
133
+ ```
134
+
135
+ ### Cancel
136
+
137
+ ```ruby
138
+ client.payment_intents.cancel(
139
+ "int_123",
140
+ idempotency_key: "order-1001-cancel"
141
+ )
142
+ ```
143
+
144
+ ### List
145
+
146
+ ```ruby
147
+ payment_intents = client.payment_intents.list(
148
+ currency: "PHP",
149
+ page_num: 0,
150
+ page_size: 20
151
+ )
152
+ ```
153
+
154
+ ## Refunds
155
+
156
+ ### Create
157
+
158
+ ```ruby
159
+ refund = client.refunds.create(
160
+ {
161
+ payment_intent_id: "int_123",
162
+ amount: 500,
163
+ reason: "requested_by_customer",
164
+ metadata: {
165
+ order_id: "ORDER-1001"
166
+ }
167
+ },
168
+ idempotency_key: "order-1001-refund-1"
169
+ )
170
+ ```
171
+
172
+ ### Retrieve
173
+
174
+ ```ruby
175
+ refund = client.refunds.retrieve("ref_123")
176
+ ```
177
+
178
+ ### List
179
+
180
+ ```ruby
181
+ refunds = client.refunds.list(
182
+ payment_intent_id: "int_123",
183
+ page_num: 0,
184
+ page_size: 20
185
+ )
186
+ ```
187
+
188
+ ## Idempotency
189
+
190
+ Use idempotency keys for payment creation, updates, cancellations, and refunds. The key should be unique per operation.
191
+
192
+ Good examples:
193
+
194
+ - `order-1001-create`
195
+ - `order-1001-update`
196
+ - `order-1001-refund-1`
197
+
198
+ Pass `idempotency_key:` to resource methods or lower-level `client.post` / `client.patch` calls. The SDK sends the key as the `x-idempotency-key` header.
199
+
200
+ ## Webhook verification
201
+
202
+ Verify incoming webhook requests using the raw request body and Airwallex signature headers:
203
+
204
+ ```ruby
205
+ raw_body = request.body.read
206
+
207
+ event = Airwallex::Webhook.construct_event(
208
+ payload: raw_body,
209
+ signature: request.headers["x-signature"],
210
+ timestamp: request.headers["x-timestamp"],
211
+ secret: ENV.fetch("AIRWALLEX_WEBHOOK_SECRET")
212
+ )
213
+
214
+ case event["name"]
215
+ when "payment_intent.succeeded"
216
+ # handle payment success
217
+ when "refund.accepted"
218
+ # handle refund accepted
219
+ end
220
+ ```
221
+
222
+ Important notes:
223
+
224
+ - Always use the raw request body.
225
+ - Verify the signature before parsing JSON (`construct_event` does this for you).
226
+ - Old timestamps are rejected by default (300 second tolerance).
227
+ - Signature comparison is timing-safe.
228
+
229
+ ## Rails installation
230
+
231
+ Run the generator:
232
+
233
+ ```bash
234
+ rails generate airwallex:install
235
+ ```
236
+
237
+ This creates:
238
+
239
+ ```
240
+ config/initializers/airwallex.rb
241
+ ```
242
+
243
+ Credentials example:
244
+
245
+ ```yaml
246
+ airwallex:
247
+ client_id: your_client_id
248
+ api_key: your_api_key
249
+ login_as: optional_account_id
250
+ webhook_secret: your_webhook_secret
251
+ ```
252
+
253
+ Rails webhook controller example:
254
+
255
+ ```ruby
256
+ class AirwallexWebhooksController < ApplicationController
257
+ skip_before_action :verify_authenticity_token
258
+
259
+ def create
260
+ event = Airwallex::Webhook.construct_event(
261
+ payload: request.body.read,
262
+ signature: request.headers["x-signature"],
263
+ timestamp: request.headers["x-timestamp"],
264
+ secret: Rails.application.credentials.dig(:airwallex, :webhook_secret) || ENV.fetch("AIRWALLEX_WEBHOOK_SECRET")
265
+ )
266
+
267
+ case event["name"]
268
+ when "payment_intent.succeeded"
269
+ # handle payment success
270
+ when "refund.accepted"
271
+ # handle refund accepted
272
+ end
273
+
274
+ head :ok
275
+ rescue Airwallex::WebhookSignatureError, Airwallex::InvalidResponseError
276
+ head :bad_request
277
+ end
278
+ end
279
+ ```
280
+
281
+ Route:
282
+
283
+ ```ruby
284
+ post "/webhooks/airwallex", to: "airwallex_webhooks#create"
285
+ ```
286
+
287
+ See also [examples/rails_webhook_controller.rb](examples/rails_webhook_controller.rb).
288
+
289
+ ## Error handling
290
+
291
+ The SDK raises typed errors for configuration, authentication, HTTP status codes, timeouts, invalid responses, and webhook verification failures.
292
+
293
+ | Error | Description |
294
+ |-------|-------------|
295
+ | `Airwallex::Error` | Base error for all SDK errors |
296
+ | `Airwallex::ConfigurationError` | Missing or invalid configuration |
297
+ | `Airwallex::AuthenticationError` | Login or token handling failed |
298
+ | `Airwallex::ArgumentError` | Invalid method arguments |
299
+ | `Airwallex::HTTPError` | Base class for HTTP error responses |
300
+ | `Airwallex::BadRequestError` | HTTP 400 |
301
+ | `Airwallex::UnauthorizedError` | HTTP 401 |
302
+ | `Airwallex::ForbiddenError` | HTTP 403 |
303
+ | `Airwallex::NotFoundError` | HTTP 404 |
304
+ | `Airwallex::ConflictError` | HTTP 409 |
305
+ | `Airwallex::RateLimitError` | HTTP 429 |
306
+ | `Airwallex::ServerError` | HTTP 5xx |
307
+ | `Airwallex::TimeoutError` | Request timeout or connection failure |
308
+ | `Airwallex::InvalidResponseError` | Invalid JSON or webhook payload |
309
+ | `Airwallex::WebhookSignatureError` | Webhook signature or timestamp verification failed |
310
+
311
+ Example:
312
+
313
+ ```ruby
314
+ begin
315
+ client.payment_intents.retrieve("int_123")
316
+ rescue Airwallex::NotFoundError => e
317
+ puts e.status
318
+ puts e.code
319
+ puts e.message
320
+ rescue Airwallex::RateLimitError
321
+ # retry later
322
+ rescue Airwallex::Error => e
323
+ # generic Airwallex SDK error
324
+ end
325
+ ```
326
+
327
+ ## Release / Local Installation
328
+
329
+ Build the gem from a checkout:
330
+
331
+ ```bash
332
+ gem build airwallex-ruby.gemspec
333
+ ```
334
+
335
+ Or use the Rake task (output goes to `pkg/`):
336
+
337
+ ```bash
338
+ bundle exec rake build
339
+ ```
340
+
341
+ Install locally:
342
+
343
+ ```bash
344
+ gem install ./airwallex-ruby-0.1.0.gem
345
+ # or, after rake build:
346
+ gem install ./pkg/airwallex-ruby-0.1.0.gem
347
+ ```
348
+
349
+ Test in IRB:
350
+
351
+ ```ruby
352
+ require "airwallex"
353
+ Airwallex::VERSION
354
+ # => "0.1.0"
355
+ ```
356
+
357
+ ## Publishing
358
+
359
+ Do not publish until the release checklist is complete and changes have been reviewed.
360
+
361
+ ### Automated (recommended)
362
+
363
+ After review, tag and push. GitHub Actions publishes to RubyGems and creates a GitHub Release:
364
+
365
+ ```bash
366
+ git tag v0.1.0
367
+ git push origin main
368
+ git push origin v0.1.0
369
+ ```
370
+
371
+ Requires the `RUBYGEMS_API_KEY` repository secret. See [docs/release.md](docs/release.md).
372
+
373
+ ### Manual
374
+
375
+ ```bash
376
+ gem push airwallex-ruby-0.1.0.gem
377
+ # or, after rake build:
378
+ gem push pkg/airwallex-ruby-0.1.0.gem
379
+ ```
380
+
381
+ See [docs/release.md](docs/release.md) for the full release checklist.
382
+
383
+ ## Development
384
+
385
+ ```bash
386
+ bundle install
387
+ bundle exec rspec
388
+ bundle exec rubocop
389
+ bundle exec rake build
390
+ ```
391
+
392
+ See [docs/airwallex-research.md](docs/airwallex-research.md) for API notes and planned resources.
393
+
394
+ ## Testing
395
+
396
+ - RSpec is used for the test suite.
397
+ - WebMock is used to stub Airwallex API requests.
398
+ - No real Airwallex credentials are required for unit tests.
399
+
400
+ ## Roadmap
401
+
402
+ - PaymentIntent confirm/capture support
403
+ - PaymentAttempts resource
404
+ - Customers resource
405
+ - PaymentConsents resource
406
+ - Transfers resource
407
+ - Balances resource
408
+ - Transactions resource
409
+ - File uploads
410
+ - More Rails generators
411
+ - Integration test mode
412
+
413
+ ## Contributing
414
+
415
+ 1. Fork the repository
416
+ 2. Create a feature branch
417
+ 3. Add specs for your changes
418
+ 4. Run the test suite (`bundle exec rspec`) and RuboCop (`bundle exec rubocop`)
419
+ 5. Open a pull request
420
+
421
+ ## Examples
422
+
423
+ Runnable and copy-paste examples live in the [examples/](examples/) directory:
424
+
425
+ - [basic_configuration.rb](examples/basic_configuration.rb)
426
+ - [payment_intent_create.rb](examples/payment_intent_create.rb)
427
+ - [refund_create.rb](examples/refund_create.rb)
428
+ - [webhook_verification.rb](examples/webhook_verification.rb)
429
+ - [rails_webhook_controller.rb](examples/rails_webhook_controller.rb)
430
+
431
+ ## License
432
+
433
+ MIT — see [LICENSE.txt](LICENSE.txt).
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
+ task :rubocop do
9
+ sh "bundle exec rubocop"
10
+ end
11
+
12
+ task default: %i[rubocop spec]
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/airwallex/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "airwallex-ruby"
7
+ spec.version = Airwallex::VERSION
8
+ spec.authors = ["James Martin Dela Cruz"]
9
+ spec.email = ["delacruzjamesmartin@gmail.com"]
10
+
11
+ spec.summary = "Unofficial Ruby SDK for Airwallex APIs"
12
+ spec.description = "An unofficial Ruby client library for Airwallex payment, payout, and treasury APIs."
13
+ spec.homepage = "https://github.com/delacruzjames/airwallex-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
20
+ spec.metadata["rubygems_mfa_required"] = "true"
21
+
22
+ spec.files = Dir.chdir(__dir__) do
23
+ files = if system("git rev-parse --is-inside-work-tree >/dev/null 2>&1")
24
+ `git ls-files -z`.split("\x0").reject do |path|
25
+ path.start_with?("spec/", ".github/") ||
26
+ path == "Gemfile.lock" ||
27
+ path.end_with?(".gem") ||
28
+ path.start_with?(".env")
29
+ end
30
+ end
31
+
32
+ if files.nil? || files.empty?
33
+ files = Dir["{lib,docs}/**/*", "README.md", "CHANGELOG.md", "LICENSE.txt", "airwallex-ruby.gemspec"]
34
+ end
35
+
36
+ %w[README.md CHANGELOG.md LICENSE.txt docs/release.md].each do |path|
37
+ files << path if File.exist?(path) && !files.include?(path)
38
+ end
39
+
40
+ files
41
+ end
42
+
43
+ spec.require_paths = ["lib"]
44
+
45
+ spec.add_dependency "faraday", "~> 2.0"
46
+ spec.add_dependency "json", "~> 2.0"
47
+
48
+ spec.add_development_dependency "dotenv", "~> 3.0"
49
+ spec.add_development_dependency "rails", ">= 7.2", "< 8"
50
+ spec.add_development_dependency "rake", "~> 13.0"
51
+ spec.add_development_dependency "rspec", "~> 3.13"
52
+ spec.add_development_dependency "rubocop", "~> 1.75"
53
+ spec.add_development_dependency "webmock", "~> 3.23"
54
+ end