This repository houses the official ruby client for Recurly's V3 API.

Installing

In your Gemfile, add recurly as a dependency.

gem 'recurly', '~> 4.42'

Note: We try to follow semantic versioning and will only apply breaking changes to major versions.

Creating a client

A client represents a connection to the Recurly servers. Every call to the server exists as a method on this class. To initialize, you only need the private API key which can be obtained on the API Credentials Page.

API_KEY = '83749879bbde395b5fe0cc1a5abf8e5'
client = Recurly::Client.new(api_key: API_KEY)
sub = client.get_subscription(subscription_id: 'abcd123456')

To access Recurly API in Europe, you will need to specify the EU Region in the argument region.

API_KEY = '83749879bbde395b5fe0cc1a5abf8e5'
client = Recurly::Client.new(api_key: API_KEY, region: :eu)
sub = client.get_subscription(subscription_id: 'abcd123456')

You can also pass the initializer a block. This will give you a client scoped for just that block:

Recurly::Client.new(api_key: API_KEY) do |client|
  sub = client.get_subscription(subscription_id: 'abcd123456')
end

If you plan on using the client for more than one site, you should initialize a new client for each site.

client = Recurly::Client.new(api_key: API_KEY1) 
sub = client.get_subscription(subscription_id: 'abcd123456')

# you should create a new client to connect to another site
client = Recurly::Client.new(api_key: API_KEY2) 
sub = client.get_subscription(subscription_id: 'abcd7890')

Logging

The client constructor optionally accepts a logger provided by the programmer. The logger you pass should be an instance of ruby stdlib's Logger or follow the same interface. By default, the client creates a logger to STDOUT with level WARN.

require 'logger'

# Create a logger to STDOUT
logger = Logger.new(STDOUT)
logger.level = Logger::INFO

# You could also use an existing logger
# If you are using Rails you may want to use your application's logger
logger = Rails.logger

client = Recurly::Client.new(api_key: API_KEY, logger: logger)

SECURITY WARNING: The log level should never be set to DEBUG in production. This could potentially result in sensitive data in your logging system.

Operations

The Recurly::Client contains every operation you can perform on the site as a list of methods. Each method is documented explaining the types and descriptions for each input and return type. You can view all available operations by looking at the Instance Methods Summary list on the Recurly::Client documentation page. Clicking a method will give you detailed information about its inputs and returns. Take the create_account operation as an example: Recurly::Client#create_account.

Pagination

Pagination is done by the class Recurly::Pager. All list_* methods on the client return an instance of this class. The pager has an each method which accepts a block for each object in the entire list. Each page is fetched automatically for you presenting the elements as a single enumerable.

plans = client.list_plans()
plans.each do |plan|
  puts "Plan: #{plan.id}"
end

You may also paginate in chunks with each_page.

plans = client.list_plans()
plans.each_page do |data|
  data.each do |plan|
    puts "Plan: #{plan.id}"
  end
end

Both Pager#each and Pager#each_page return Enumerators if a block is not given. This allows you to use other Enumerator methods such as map or each_with_index.

plans = client.list_plans()
plans.each_page.each_with_index do |data, page_num|
  puts "Page Number #{page_num}"
  data.each do |plan|
    puts "Plan: #{plan.id}"
  end
end

Pagination endpoints take a number of options to sort and filter the results. They can be passed in as a hash provided by the :params keyword argument. The names, types, and descriptions of these arguments are listed in the rubydocs for each method:

options = {
  params: {
    limit: 200, # number of items per page
    state: :active, # only active plans
    sort: :updated_at,
    order: :asc,
    begin_time: DateTime.new(2017,1,1), # January 1st 2017,
    end_time: DateTime.now
  }
}

plans = client.list_plans(**options)
plans.each do |plan|
  puts "Plan: #{plan.id}"
end

A note on limit:

limit defaults to 20 items per page and can be set from 1 to 200. Choosing a lower limit means more network requests but smaller payloads. We recommend keeping the default for most cases but increasing the limit if you are planning on iterating through many pages of items (e.g. all transactions in your site).

Efficiently Fetch the First or Last Resource

The Pager class implements a first method which allows you to fetch just the first or last resource from the server. On top of being a convenient abstraction, this is implemented efficiently by only asking the server for the 1 item you want.

accounts = client.list_accounts(
  subscriber: true,
  order: :desc
)

last_subscriber = accounts.first

If you want to fetch the last account in this scenario, invert the order from descending desc to ascending asc:

accounts = client.list_accounts(
  subscriber: true,
  order: :asc
)

first_subscriber = accounts.first

Counting Resources

The Pager class implements a count method which allows you to count the resources the pager would return. It does so by calling the endpoint with HEAD and parsing and returning the Recurly-Total-Records header. This method respects any filtering parameters you apply to the pager, but the sorting parameters will have no effect.

accounts = client.list_accounts(
  subscriber: true,
  begin_time: DateTime.new(2017,1,1)
)

# Calling count here will return an integer indicating
# the number of subscribers since 2017
count = accounts.count
# => 573

Creating Resources

Currently, resources are created by passing in a body keyword argument in the form of a Hash. This Hash must follow the schema of the documented request type. For example, the create_plan operation takes a request of type Recurly::Requests::PlanCreate. Failing to conform to this schema will result in an argument error.

require 'securerandom'

code = SecureRandom.uuid
plan_data = {
  code: code,
  interval_length: 1,
  interval_unit: 'months',
  name: code,
  currencies: [
    {
      currency: 'USD',
      setup_fee: 800,
      unit_amount: 10
    }
  ]
}

plan = client.create_plan(body: plan_data)

Error Handling

All errors thrown by this library are based off of the Recurly::Errors::APIError. There

This library throws one main type of exception, Recurly::Errors::APIError. There exists an additional hierarchy of errors to facilitate the process of rescuing various classes of errors. More detail can be found in the Api Errors Module.

You can catch specific or generic versions of these exceptions. Example:

begin
  client = Recurly::Client.new(api_key: API_KEY)
  code = "iexistalready"
  plan_data = {
    code: code,
    interval_length: 1,
    interval_unit: 'months',
    name: code,
    currencies: [
      {
        currency: 'USD',
        setup_fee: 800,
        unit_amount: 10
      }
    ]
  }

  plan = client.create_plan(body: plan_data)
rescue Recurly::Errors::ValidationError => ex
  puts ex.inspect
  #=> #<Recurly::ValidationError: Recurly::ValidationError: Code 'iexistalready' already exists>
  puts ex.recurly_error.inspect
  #=> #<Recurly::Error:0x007fbbdf8a32c8 @attributes={:type=>"validation", :message=>"Code 'iexistalready' already exists", :params=>[{"param"=>"code", "message"=>"'iexistalready' already exists"}]}>
  puts ex.status_code
  #=> 422
rescue Recurly::Errors::TimeoutError => ex
  # catch a specific server error
rescue Recurly::Errors::ServerError => ex
  # catch a generic server error
rescue Recurly::Errors::APIError => ex
  # catch a generic api error
end

Recurly::Errors::APIError instances provide access to the response via the #get_response method.

HTTP Metadata

Sometimes you might want to get some additional information about the underlying HTTP request and response. Instead of returning this information directly and forcing the programmer to unwrap it, we inject this metadata into the top level resource that was returned. You can access the Recurly::HTTP::Response by calling #get_response on any Recurly::Resource.

Warning: Do not log or render whole requests or responses as they may contain PII or sensitive data.

 = @client.(account_id: "code-benjamin")
response = .get_response
response.rate_limit_remaining #=> 1985
response.request_id #=> "0av50sm5l2n2gkf88ehg"
response.request.path #=> "/sites/subdomain-mysite/accounts/code-benjamin"
response.request.body #=> None

This also works on Recurly::Resources::Empty responses:

response = @client.remove_line_item(
  line_item_id: "a959576b2b10b012"
).get_response

And it can be captured on exceptions through the Recurly::APIError object:

begin
   = client.(account_id: "code-benjamin")
rescue Recurly::Errors::NotFoundError => e
  response = e.get_response()
  puts "Give this request id to Recurly Support: #{response.request_id}"
end

Webhooks

Recurly can send webhooks to any publicly accessible server. When an event in Recurly triggers a webhook (e.g., an account is opened), Recurly will attempt to send this notification to the endpoint(s) you specify. You can specify up to 10 endpoints through the application. All notifications will be sent to all configured endpoints for your site.

See our product docs to learn more about webhooks and see our dev docs to learn about what payloads are available.

Although our API is now JSON, our webhook payloads are still formatted as XML for the time being. This library is not yet responsible for handling webhooks. If you do need webhooks, we recommend using a simple XML to Hash parser.

If you are using Rails, we'd recommend Hash.from_xml.

notification = Hash.from_xml <<-XML
  <?xml version="1.0" encoding="UTF-8"?>
  <new_account_notification>
    <account>
      <account_code>1</account_code>
      <username nil="true"></username>
      <email>[email protected]</email>
      <first_name>Verena</first_name>
      <last_name>Example</last_name>
      <company_name nil="true"></company_name>
    </account>
  </new_account_notification>
XML

code = notification["new_account_notification"]["account"]["account_code"]
puts "New Account with code #{code} created."

If you are not using Rails, we recommend you use nokogiri; however, heed security warnings about parse options. Although the XML should only be coming from Recurly, you should parse it as untrusted to be safe. Read more about the security implications of parsing untrusted XML in this OWASP cheatsheet.