Airwallex Ruby Gem
A Ruby client library for the Airwallex API, providing access to payment acceptance and payout capabilities.
Overview
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.
Current Features (v0.1.0):
- Authentication: Bearer token authentication with automatic refresh
- Payment Acceptance: Payment intent creation, confirmation, and management
- Payouts: Transfer creation and beneficiary management
- Idempotency: Automatic request deduplication for safe retries
- Pagination: Unified interface over cursor-based and offset-based pagination
- Webhook Security: HMAC-SHA256 signature verification with replay protection
- Sandbox Support: Full testing environment for development
Note: This is an initial MVP release. Additional resources (FX, cards, refunds, etc.) will be added in future versions.
Installation
Add this line to your application’s Gemfile:
ruby
gem 'airwallex'
And then execute:
bash
bundle install
Or install it yourself as:
bash
gem install airwallex
Quick Start
Configuration
```ruby require ‘airwallex’
Airwallex.configure do |config| config.api_key = ‘your_api_key’ config.client_id = ‘your_client_id’ config.environment = :sandbox # or :production end ```
Creating a Payment Intent
```ruby # Create a payment intent payment_intent = Airwallex::PaymentIntent.create( amount: 100.00, currency: ‘USD’, merchant_order_id: ‘order_123’, return_url: ‘https://yoursite.com/return’ )
Confirm with card details
payment_intent.confirm( payment_method: { type: ‘card’, card: { number: ‘4242424242424242’, expiry_month: ‘12’, expiry_year: ‘2025’, cvc: ‘123’ } } ) ```
Creating a Payout
```ruby # Create a beneficiary beneficiary = Airwallex::Beneficiary.create( bank_details: { account_number: ‘123456789’, account_routing_type1: ‘aba’, account_routing_value1: ‘026009593’, bank_country_code: ‘US’ }, beneficiary_type: ‘BUSINESS’, company_name: ‘Acme Corp’ )
Execute transfer
transfer = Airwallex::Transfer.create( beneficiary_id: beneficiary.id, source_currency: ‘USD’, transfer_method: ‘LOCAL’, amount: 1000.00, reason: ‘Payment for services’ ) ```
Processing Refunds
```ruby # Create a full refund refund = Airwallex::Refund.create( payment_intent_id: payment_intent.id, amount: 100.00, reason: ‘requested_by_customer’ )
Create a partial refund
partial_refund = Airwallex::Refund.create( payment_intent_id: payment_intent.id, amount: 50.00 )
List all refunds for a payment
refunds = Airwallex::Refund.list(payment_intent_id: payment_intent.id) ```
Managing Payment Methods
```ruby # Create a customer customer = Airwallex::Customer.create( email: ‘[email protected]’, first_name: ‘John’, last_name: ‘Doe’ )
Save a payment method
payment_method = Airwallex::PaymentMethod.create( type: ‘card’, card: { number: ‘4242424242424242’, expiry_month: ‘12’, expiry_year: ‘2025’, cvc: ‘123’ }, billing: { first_name: ‘John’, email: ‘[email protected]’ } )
Use saved payment method
payment_intent.confirm(payment_method_id: payment_method.id)
List customer’s payment methods
methods = customer.payment_methods ```
Batch Transfers
```ruby # Create a batch of transfers for bulk payouts batch = Airwallex::BatchTransfer.create( request_id: “batch_#Time.now.to_i”, source_currency: ‘USD’, transfers: [ { beneficiary_id: ‘ben_001’, amount: 100.00, reason: ‘Seller payout’ }, { beneficiary_id: ‘ben_002’, amount: 250.00, reason: ‘Affiliate payment’ }, { beneficiary_id: ‘ben_003’, amount: 500.00, reason: ‘Vendor payment’ } ] )
Check batch status
batch = Airwallex::BatchTransfer.retrieve(batch.id) puts “Completed: #batchbatch.success_count/#batchbatch.total_count”
Check individual transfer statuses
batch.transfers.each do |transfer| puts “#transfertransfer.id: #transfertransfer.status” end ```
Managing Disputes
```ruby # List all open disputes disputes = Airwallex::Dispute.list(status: ‘OPEN’)
Get specific dispute
dispute = Airwallex::Dispute.retrieve(‘dis_123’) puts “Dispute amount: #disputedispute.amount #disputedispute.currency” puts “Reason: #disputedispute.reason” puts “Evidence due: #disputedispute.evidence_due_by”
Submit evidence to challenge
dispute.submit_evidence( customer_communication: ‘Email showing delivery confirmation’, shipping_tracking_number: ‘1Z999AA10123456784’, shipping_documentation: ‘Proof of delivery with signature’ )
Or accept dispute without challenging
dispute.accept ```
Foreign Exchange & Multi-Currency
```ruby # Get real-time exchange rate rate = Airwallex::Rate.retrieve( buy_currency: ‘EUR’, sell_currency: ‘USD’ ) puts “Current rate: #raterate.client_rate”
Lock in a rate with a quote (valid for 24 hours)
quote = Airwallex::Quote.create( buy_currency: ‘EUR’, sell_currency: ‘USD’, sell_amount: 10000.00, validity: ‘HR_24’ )
puts “Locked rate: #quotequote.client_rate” puts “Expires in: #quotequote.seconds_until_expiration seconds” puts “Is expired? #quotequote.expired?”
Execute conversion using locked quote
conversion = Airwallex::Conversion.create( quote_id: quote.id, reason: ‘Multi-currency settlement’ )
Or convert at current market rate
conversion = Airwallex::Conversion.create( buy_currency: ‘EUR’, sell_currency: ‘USD’, sell_amount: 5000.00, reason: ‘Currency exchange’ )
Check account balances
balances = Airwallex::Balance.list balances.each do |balance| next if balance.available_amount <= 0 puts “#balancebalance.currency: #balancebalance.available_amount available” end
Get specific currency balance
usd_balance = Airwallex::Balance.retrieve(‘USD’) puts “USD Available: #usd_balanceusd_balance.available_amount” puts “USD Total: #usd_balanceusd_balance.total_amount” ```
Usage
Authentication
The gem uses Bearer token authentication with automatic token refresh:
ruby
Airwallex.configure do |config|
config.api_key = 'your_api_key'
config.client_id = 'your_client_id'
config.environment = :sandbox # or :production
end
Tokens are automatically refreshed when they expire, and the gem handles thread-safe token management.
Idempotency
The gem automatically handles idempotency for safe retries:
```ruby # Automatic request_id generation transfer = Airwallex::Transfer.create( amount: 500.00, beneficiary_id: ‘ben_123’ # request_id automatically generated )
Or provide your own for reconciliation
transfer = Airwallex::Transfer.create( amount: 500.00, beneficiary_id: ‘ben_123’, request_id: ‘my_internal_id_789’ ) ```
Pagination
Unified interface across both cursor-based and offset-based endpoints:
```ruby # Auto-pagination with enumerable Airwallex::Transfer.list.auto_paging_each do |transfer| puts transfer.id end
Manual pagination
transfers = Airwallex::Transfer.list(page_size: 50) while transfers.has_more? transfers.each { |t| process(t) } transfers = transfers.next_page end ```
Webhook Handling
```ruby # In your webhook controller payload = request.body.read signature = request.headers[‘x-signature’] timestamp = request.headers[‘x-timestamp’]
begin event = Airwallex::Webhook.construct_event( payload, signature, timestamp, tolerance: 300 # 5 minutes )
case event.type when ‘payment_intent.succeeded’ handle_successful_payment(event.data) when ‘payout.transfer.failed’ handle_failed_payout(event.data) end rescue Airwallex::SignatureVerificationError => e # Invalid signature head :bad_request end ```
Error Handling
ruby
begin
transfer = Airwallex::Transfer.create(params)
rescue Airwallex::InsufficientFundsError => e
# Handle insufficient balance
notify_user("Insufficient funds: #{e.message}")
rescue Airwallex::RateLimitError => e
# Rate limit hit - automatic retry with backoff
retry_with_backoff
rescue Airwallex::AuthenticationError => e
# Invalid credentials
log_error("Auth failed: #{e.message}")
rescue Airwallex::APIError => e
# General API error
log_error("API error: #{e.code} - #{e.message}")
end
Architecture
Design Principles
- Correctness First: Automatic idempotency and type safety prevent duplicate transactions
- Fail-Safe Defaults: Sandbox environment default, automatic token refresh
- Developer Experience: Auto-pagination, dynamic schema validation, structured errors
- Security: HMAC webhook verification, constant-time signature comparison, SCA support
- Resilience: Exponential backoff, jittered retries, concurrent request limits
Core Components
lib/airwallex/
├── api_operations/ # CRUD operation mixins (Create, Retrieve, List, Update, Delete)
├── resources/ # Implemented resources
│ ├── payment_intent.rb # Payment acceptance
│ ├── transfer.rb # Payouts
│ └── beneficiary.rb # Payout beneficiaries
├── api_resource.rb # Base resource class with dynamic attributes
├── list_object.rb # Pagination wrapper
├── errors.rb # Exception hierarchy
├── client.rb # HTTP client with authentication
├── configuration.rb # Environment and credentials
├── webhook.rb # Signature verification
├── util.rb # Helper methods
└── middleware/ # Faraday middleware
└── idempotency.rb # Automatic request_id injection
Development
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.
Running Tests
bash
bundle exec rspec
Code Style
bash
bundle exec rubocop
Local Development
```ruby # In bin/console or irb require ‘airwallex’
Airwallex.configure do |config| config.environment = :sandbox config.api_key = ENV[‘AIRWALLEX_API_KEY’] config.client_id = ENV[‘AIRWALLEX_CLIENT_ID’] end ```
API Coverage
Currently Implemented Resources
- Payment Acceptance:
- PaymentIntent (create, retrieve, list, update, confirm, cancel, capture)
- Refund (create, retrieve, list)
- PaymentMethod (create, retrieve, list, update, delete, detach)
- Customer (create, retrieve, list, update, delete)
- Dispute (retrieve, list, accept, submit_evidence)
- Payouts:
- Transfer (create, retrieve, list, cancel)
- Beneficiary (create, retrieve, list, delete)
- BatchTransfer (create, retrieve, list)
- Foreign Exchange & Multi-Currency:
- Rate (retrieve, list) - Real-time exchange rate queries
- Quote (create, retrieve) - Lock exchange rates with expiration tracking
- Conversion (create, retrieve, list) - Execute currency conversions
- Balance (list, retrieve) - Query account balances across currencies
- Webhooks: Event handling, HMAC-SHA256 signature verification
Coming in Future Versions
- Global accounts
- Card issuing
- Subscriptions and billing
- Virtual account numbers
Environment Support
Sandbox
Testing environment for development:
ruby
Airwallex.configure do |config|
config.environment = :sandbox
config.api_key = ENV['AIRWALLEX_SANDBOX_API_KEY']
config.client_id = ENV['AIRWALLEX_SANDBOX_CLIENT_ID']
end
Production
Live environment for real financial transactions:
ruby
Airwallex.configure do |config|
config.environment = :production
config.api_key = ENV['AIRWALLEX_API_KEY']
config.client_id = ENV['AIRWALLEX_CLIENT_ID']
end
Rate Limits
The gem respects Airwallex API rate limits. If you encounter Airwallex::RateLimitError, implement retry logic with exponential backoff:
ruby
begin
transfer = Airwallex::Transfer.create(params)
rescue Airwallex::RateLimitError => e
sleep(2 ** retry_count)
retry_count += 1
retry if retry_count < 3
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/Sentia/airwallex.
Development Setup
- Fork and clone the repository
- Run
bin/setupto install dependencies - Create a
.envfile with sandbox credentials - Run tests:
bundle exec rspec - Check style:
bundle exec rubocop
Guidelines
- Write tests for new features
- Follow existing code style (enforced by Rubocop)
- Update documentation for API changes
- Ensure all tests pass before submitting PR
Versioning
This gem follows Semantic Versioning. The Airwallex API uses date-based versioning, which is handled internally by the gem.
Security
If you discover a security vulnerability, please email [email protected] instead of using the issue tracker.
Documentation
Requirements
- Ruby 3.1 or higher
- Bundler 2.0 or higher
Dependencies
faraday(~> 2.0) - HTTP clientfaraday-retry- Request retry logicfaraday-multipart- File upload support
License
The gem is available as open source under the terms of the MIT License.
Support
- GitHub Issues: https://github.com/Sentia/airwallex/issues
- Airwallex Support: https://www.airwallex.com/support
Acknowledgments
Built with comprehensive analysis of the Airwallex API ecosystem. Special thanks to the Airwallex team for their extensive documentation and developer resources.