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

  1. Fork and clone the repository
  2. Run bin/setup to install dependencies
  3. Create a .env file with sandbox credentials
  4. Run tests: bundle exec rspec
  5. 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 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

Acknowledgments

Built with comprehensive analysis of the Airwallex API ecosystem. Special thanks to the Airwallex team for their extensive documentation and developer resources.