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:
gem 'airwallex'And then execute:
bundle installOr install it yourself as:
gem install airwallexQuick Start
Configuration
require 'airwallex'
Airwallex.configure do |config|
config.api_key = 'your_api_key'
config.client_id = 'your_client_id'
config.environment = :sandbox # or :production
endCreating a Payment Intent
# 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
# 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
# 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
# Create a customer
customer = Airwallex::Customer.create(
email: 'customer@example.com',
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: 'customer@example.com'
}
)
# Use saved payment method
payment_intent.confirm(payment_method_id: payment_method.id)
# List customer's payment methods
methods = customer.payment_methodsBatch Transfers
# 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: #{batch.success_count}/#{batch.total_count}"
# Check individual transfer statuses
batch.transfers.each do |transfer|
puts "#{transfer.id}: #{transfer.status}"
endManaging Disputes
# List all open disputes
disputes = Airwallex::Dispute.list(status: 'OPEN')
# Get specific dispute
dispute = Airwallex::Dispute.retrieve('dis_123')
puts "Dispute amount: #{dispute.amount} #{dispute.currency}"
puts "Reason: #{dispute.reason}"
puts "Evidence due: #{dispute.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.acceptForeign Exchange & Multi-Currency
# Get real-time exchange rate
rate = Airwallex::Rate.retrieve(
buy_currency: 'EUR',
sell_currency: 'USD'
)
puts "Current rate: #{rate.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: #{quote.client_rate}"
puts "Expires in: #{quote.seconds_until_expiration} seconds"
puts "Is expired? #{quote.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 "#{balance.currency}: #{balance.available_amount} available"
end
# Get specific currency balance
usd_balance = Airwallex::Balance.retrieve('USD')
puts "USD Available: #{usd_balance.available_amount}"
puts "USD Total: #{usd_balance.total_amount}"Usage
Authentication
The gem uses Bearer token authentication with automatic token refresh:
Airwallex.configure do |config|
config.api_key = 'your_api_key'
config.client_id = 'your_client_id'
config.environment = :sandbox # or :production
endTokens are automatically refreshed when they expire, and the gem handles thread-safe token management.
Idempotency
The gem automatically handles idempotency for safe retries:
# 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:
# 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
endWebhook Handling
# 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
endError Handling
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}")
endArchitecture
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
bundle exec rspecCode Style
bundle exec rubocopLocal Development
# 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']
endAPI 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:
Airwallex.configure do |config|
config.environment = :sandbox
config.api_key = ENV['AIRWALLEX_SANDBOX_API_KEY']
config.client_id = ENV['AIRWALLEX_SANDBOX_CLIENT_ID']
endProduction
Live environment for real financial transactions:
Airwallex.configure do |config|
config.environment = :production
config.api_key = ENV['AIRWALLEX_API_KEY']
config.client_id = ENV['AIRWALLEX_CLIENT_ID']
endRate Limits
The gem respects Airwallex API rate limits. If you encounter Airwallex::RateLimitError, implement retry logic with exponential backoff:
begin
transfer = Airwallex::Transfer.create(params)
rescue Airwallex::RateLimitError => e
sleep(2 ** retry_count)
retry_count += 1
retry if retry_count < 3
endContributing
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 security@sentia.com instead of using the issue tracker.
Documentation
Requirements
- Ruby 3.1 or higher
- Bundler 2.0 or higher
Dependencies
-
faraday(~> 2.0) - HTTP client -
faraday-retry- Request retry logic -
faraday-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.