Introduction
An Erlang/LFE Client for the Recurly Billing REST API
From the Recurly docs site:
“Recurly provides a complete recurring billing system designed to remove all the headaches from subscription billing. Whether you’ve integrated with our Hosted Payment Pages, Recurly.js embedded forms, or API, these documents will help manage your day-to-day billing.”
The LFE library for the Recurly REST service was created in order to support the billing needs of LFE, Erlang, and other BEAM language applications that need to offer billing features.
Getting Started
Installation
Just add the rcrly repo to your
rebar.config
deps:
{deps, [
...
{rcrly, ".*",
{git, "git@github.com:cinova/rcrly.git", "master"}}
]}.
Or, if you use
lfe.config
:
#(project (#(deps (#("cinova/rcrly" "master")))))
And then do the usual:
$ make compile
To install the LFE rcrly REST client library, follow the standard rebar practics or use an lfe.config
file.
Configuration
This project comes with a sample configuration file you can copy and then edit:
$ cp sample-lfe.ini ~/.rcrly/lfe.ini
Or you can just use the following as a template:
[REST API]
key = GFEDCBA9876543210
host = yourname.recurly.com
timeout = 10000
version = v2
The LFE rcrly library supports two modes of configuration:
- OS environment variables
- the use of
~/.rcrly/lfe.ini
OS environment variables take precedence over values in the configuration file. If you would like to use environment variables, the following may be set:
RECURLY_API_KEY
RECURLY_HOST
(e.g.,yourname.recurly.com
)RECURLY_DEFAULT_CURRENCY
RECURLY_VERSION
RECURLY_REQUEST_TIMEOUT
You have the option of using values stored in a configuration file, instead.
Starting rcrly
Execute the following before using rcrly, when not using the
make
targets which start it automatically:
> (rcrly:start)
(#(gproc ok)
#(econfig ok)
#(inets ok)
#(ssl ok)
#(lhttpc ok))
The make
targets for both the LFE REPL and the Erlang shell start rcrly
automatically. If you didn’t use either of those, then you will need to
start rcrly manually.
If you’re not in the REPL and you will be using this library programmatically, you will want to make that call when your own application starts.
Authentication
In your OS shell, export your Recurly API key and your subdomain, e.g.:
$ export RECURLY_API_KEY=GFEDCBA9876543210
$ export RECURLY_HOST=yourname.recurly.com
Or be sure to have these defined in your
~/.rcrly/lfe.ini
file:
[REST API]
key = GFEDCBA9876543210
host = yourname.recurly.com
Recurly clients authenticate by setting HTTP headers. The rcrly library does this for you automatically by extracting the necessary data from OS environment variables or from your rcrly configuration file.
Making Calls
Making calls from LFE are pretty standard:
> (rcrly:get-accounts)
Which gives results like the following:
#(ok
(#(account ...)
#(account ...)
...))
Erlang calls are done in the usual way, escaping hypenated LFE atoms as necessary:
1> rcrly:'get-accounts'().
Which will give results in the following form:
{ok,[{account, [...]},
{account, [...]},
...]}
As aluded to above, even though rcrly is written in LFE, it can be used from an BEAM language which supports Core Erlang.
Options
Every rcrly get
and post
API call takes an optional final positional argument which supprts
keyword arguments. The following are supported (or planned) such options:
return-type
- what format the client calls should take. Can be one ofdata
,full
, orxml
; the default isdata
.log-level
- sets the log level on-the-fly, for easy debugging on a per-request basisendpoint
- whether the request being made is against an API endpoint or a raw URL (defaults totrue
)batch-size
- [NOT YET SUPPORTED] an integer between1
and200
representing the number of results returned in the Recurly service responses; defaults to20
.follow-links
- [NOT YET SUPPORTED] a boolean representing whether linked data should be automatically quereied and added to the results; defaults tofalse
.
return-type
When the
return-type
is set todata
(the default), the data from the response is what is returned:
> (rcrly:get-account 1 '(#(return-type data)))
Results:
#(ok
(#(adjustments ...)
#(invoices ...)
#(subscriptions ...)
#(transactions ...)
#(account_code () ("1"))
...
#(address ...)
...))
When the
return-type
is set tofull
, the response is annotated and returned:
> (rcrly:get-account 1 '(#(return-type full)))
Results:
#(ok
(#(response ok)
#(status #(200 "OK"))
#(headers ...)
#(body
(#(tag "account")
#(attr (#(href "https://yourname.recurly.com/v2/accounts/1")))
#(content
#(account
(#(adjustments ...)
#(invoices ...)
#(subscriptions ...)
#(transactions ...)
...
#(account_code () ("1"))
...
#(address ...)
...)))
#(tail "\n")))))
When the
return-type
is set toxml
, the “raw” binary value is returned, as it is obtained fromlhttpc
, without modification or any parsing:
> (rcrly:get-account 1 '(#(return-type xml)))
Results:
#(ok
#(#(200 "OK")
(#("strict-transport-security" "max-age=15768000; includeSubDomains")
#("x-request-id" "ac52s06cmfugp9oauclg")
#("cache-control" "max-age=0, private, must-revalidate")
...)
#B(60 63 120 109 108 32 118 101 114 115 105 111 110 ...)))
The rcrly client library lets you specify the format of the returned results by setting the value of the return-type
option.
log-level
Set a new log level:
> (rcrly:get-account 1 '(#(log-level debug)))
At any point you may change the log level for the client.
endpoint
Normal “endpoint” call:
> (rcrly:get "/some/recurly/endpoint")
Set the
endpoint
option tofalse
to make a direct, “URL” call:
> (set options '(#(endpoint false)))
> (rcrly:get "https://some.domain/path/to/resource" options)
If you wish to make a request to a full URL, you will need to pass the option
#(endpoint false)
to override the default behaviour of the rcrly library
creating the URL for you, based upon the provided endpoint.
batch-size
TBD
follow-links
TBD
Options for lhttpc
If you wish to pass general HTTP client options to lhttpc, then you will need to use
rcrly-httpc:request/7
, which takes the arguments endpoint
, method
,
headers
, body
, timeout
, options
, and lhttpc-options
.
The options
parameter is for the rcrly options discussed above, and lhttpc-options
are the regular lhttpc options, the most significant of which are:
connect_options
- a list of termssend_retry
- an integerpartial_upload
- an integer (window size)partial_download
- a list of one or both of#(window_size N)
and#(part_size N)
proxy
- a URL stringproxy_ssl_options
- a list of termspool
- pid or atom
Creating Payloads
To demonstrate creating XML,
slurp
the following file:
> (slurp "src/rcrly-xml.lfe")
#(ok rcrly-xml)
Now you can use the rcrly macros to create XML in LFE syntax:
> (xml/account (xml/company_name "Bob's Red Mill"))
"<account><company_name>Bob's Red Mill</company_name></account>"
This also works for modules that will be genereating XML payloads: simply
include-lib
them like they are inrcrly-xml
:
(include-lib "rcrly/include/xml.lfe")
Here’s a sample payload from the Recurly docs (note that multiple children need to be wrapped in a
list
):
> (xml/billing_info
(list (xml/first_name "Verena")
(xml/last_name "Example")
(xml/number "4111-1111-1111-1111")
(xml/verification_value "123")
(xml/month "11")
(xml/year "2015")))
Which produces the following result:
"<billing_info>
<first_name>Verena</first_name>
<last_name>Example</last_name>
<number>4111-1111-1111-1111</number>
<verification_value>123</verification_value>
<month>11</month>
<year>2015</year>
</billing_info>"
Payloads for PUT
and POST
data in the Recurly REST API are XML
documents. As such, we need to be able to create XML for such things as
update actions. To facilitate this, The LFE rcrly library provides
XML-generating macros. in the REPL, you can slurp
the rcrly-xml
module, and then have access to them. For instance:
Working with Results
Example form for multi-valued results:
#(ok
#(accounts
(...) ; attributes
(#(account
(...) ; attributes
(...) ; child elements
)
#(account ...)
#(account ...)
...)))
Example form for single-valued results:
#(ok
#(account
(...) ; attributes
(...) ; child elements
))
All results in rcrly are of the form #(ok ...)
or #(error ...)
, with the
elided contents of those tuples changing depending upon context. This is the
standard approach for Erlang libraries, so should be quite familiar to users.
Recurly’s API is XML-based; the rcrly API inherits some of its characteristics from this fact. In particular, data structures representing the parsed XML data are regularly returned by rcrly calls. Parsed rcrly results have the following:
- a tag
- attributes
- contents (which may itself contain nested tag/attrs/contents)
As such, many results are often 3-tuples. rcrly includes functions (see below) for working with this 3-tuple data.
The rcrly LFE library distinguishes between two different result types:
- multi-valued
- single-valued
Multi-valued results are simply results that have items in a lis. Many rcrly API
calls will return a list of items, for example, get-all-invoices/0
,
get-plans/0
, or get-accounts/0
. The rcrly library provides map
and
foldl
functions for easily working with these results.
By single-value results, we mean API calls which do not return a list of
values, but intstead return a single-item data structure. Examples of API calls
which do this are get-account/1
, get-billing-info/1
, get-plan/1
,
etc. The rcrly library provides functions like get-in
and get-linked
for
easily working with these results.
Format
A standard Recurly XML result:
<account href="https://yourname.recurly.com/v2/accounts/1">
<adjustments href="https://yourname.recurly.com/v2/accounts/1/adjustments"/>
<billing_info href="https://yourname.recurly.com/v2/accounts/1/billing_info"/>
<invoices href="https://yourname.recurly.com/v2/accounts/1/invoices"/>
<redemption href="https://yourname.recurly.com/v2/accounts/1/redemption"/>
<subscriptions href="https://yourname.recurly.com/v2/accounts/1/subscriptions"/>
<transactions href="https://yourname.recurly.com/v2/accounts/1/transactions"/>
<account_code>1</account_code>
<state>active</state>
<username nil="nil"></username>
<email>verena@example.com</email>
<first_name>Verena</first_name>
<last_name>Example</last_name>
<company_name></company_name>
<vat_number nil="nil"></vat_number>
<tax_exempt type="boolean">false</tax_exempt>
<address>
<address1>108 Main St.</address1>
<address2>Apt #3</address2>
<city>Fairville</city>
<state>WI</state>
<zip>12345</zip>
<country>US</country>
<phone nil="nil"></phone>
</address>
<accept_language nil="nil"></accept_language>
<hosted_login_token>a92468579e9c4231a6c0031c4716c01d</hosted_login_token>
<created_at type="datetime">2011-10-25T12:00:00</created_at>
</account>
Here is that same result as parsed by the LFE rcrly library:
#(account
(#(href "https://yourname.recurly.com/v2/accounts/1"))
(#(adjustments
(#(href "https://yourname.recurly.com/v2/accounts/1/adjustments"))
())
#(invoices
(#(href "https://yourname.recurly.com/v2/accounts/1/invoices"))
())
#(subscriptions
(#(href "https://yourname.recurly.com/v2/accounts/1/subscriptions"))
())
#(transactions
(#(href "https://yourname.recurly.com/v2/accounts/1/transactions"))
())
#(account_code () ("1"))
#(state () ("active"))
#(username () ())
#(email () ("verena@example.com"))
#(first_name () ("Verena"))
#(last_name () ("Example"))
#(company_name () ())
#(vat_number (#(nil "nil")) ())
#(tax_exempt (#(type "boolean")) ("false"))
#(address ()
(#(address1 () ("108 Main St."))
#(address2 () ("Apt #3"))
#(city () ("Fairville"))
#(state () ("WI"))
#(zip () ("12345"))
#(country () ("US"))
#(phone (#(nil "nil")) ())))
#(accept_language (#(nil "nil")) ())
#(hosted_login_token () ("a92468579e9c4231a6c0031c4716c01d"))
#(created_at (#(type "datetime")) ("2011-10-25T12:00:00"))))
As noted above, the format of the results depend upon what value you have passed
as the return-type
; by default, the data
type is passed and this simply
returns the data requested by the particular API call (not the headers, HTTP
status, body, XML conversion info, etc. – if you want that, you’ll need to pass
the full
value associated with the return-type
).
The API calls return XML that has been parsed and converted to LFE data structures by the erlsom library.
The rcrly library offers a couple of convenience functions for extracting data from this sort of structure – see the next two sections for more information about data extraction.
get-data
First, get some data using the
full
return type:
> (set `#(ok ,results) (rcrly:get-accounts `(#(return-type full))))
Results:
#(ok
(#(response ok)
#(status #(200 "OK"))
#(headers (...))
#(body
(#(tag "accounts")
#(attr (#(type "array")))
#(content
#(accounts ...))))))
Example
get-data
call:
> (rcrly:get-data results)
#(accounts
(#(type "array"))
(#(account ...)
#(account ...)))
The get-data
utility function is provided in the rcrly
module and is
useful for extracing response data returned from client requests made with
the full
option. It assumes a nested property list structure with the
content
key in the body
’s property list.
Though this is useful when dealing with response data from full
the return
type, you may find that it is more convenient to use the default data
return
type with the rcrly:get-in
function instead, as it allows you to extract
just the data you need. See below for an example.
get-in
Get some data:
> (set `#(ok ,account) (rcrly:get-account 1))
Results:
#(ok
#(account
(#(href ...))
(#(adjustments ...)
...
#(address ()
(...
#(city () ("Fairville"))
...))
...)))
Example
get-in
call:
> (rcrly:get-in '(account address city) account)
"Fairville"
Note that the
city
field is nested in theaddress
field. Theaddress
data is nested in theaccount
.
The utillity function rcrly:get-in
is inspired by the Clojure get-in
function, but in this case, tailored to work with the rcrly results which have
been converted from XML to LFE/Erlang data structures. With a single call, you
are able to retrieve data which is nested at any depth, providing just the keys
needed to locate it.
get-linked
Using the same account data as tthe last example, here’s how you get the linked transactions:
> (rcrly:get-linked '(account transactions) account)
#(ok
#(transactions
(#(type "array"))
(#(transaction ...)
#(transaction ...)
#(transaction ...)
...)))
In the Recurly REST API, data relationships are encoded in media links, per
common best REST practices. Linked data may be retreived easily using the
get-linked/2
utility function (analog to the get-in/2
function).
get-linked/2
performs most of the same work that get-in/2
does, with
the exception being that the last item in the list of keys points to linked
data. As such, get-linked/2
will then perform an HTTP GET
for the
linked media.
map
and foldl
Example
map/2
call that lists all the plan names in the system:
> (rcrly:map
(lambda (x)
(rcrly:get-in '(plan name) x))
(rcrly:get-plans))
Results:
("Silver Plan" "Gold plan" "30-Day Free Trial")
Example
foldl/3
call that gets the total of all invoices (ignoring currency type), starting with an “add” function:
> (defun add-invoice (invoice subtotal)
(+ subtotal
(/ (list_to_integer
(rcrly:get-in '(invoice total_in_cents) invoice))
100)))
add-invoice
Now to use that function with
rcrly:foldl/3
:
> (rcrly:foldl
#'add-invoice/2
0
(rcrly:get-all-invoices))
Result:
120.03
Recurly’s API is XML-based, so parsed results have the following: * a tag * attributes * contents (which may itself contain nested tag/attrs/contents)
The map/2
and foldl/3
functions provided by rcrly aim to make working
with these results easier, especially for iterating through multi-valued
results.
It is important to note: map/2
and foldl/3
both take a complete
result – this inlcudes the #(ok ...)
.
Composing Results
To use the
->>
macro we’ll need to slurp a file that has included it:
> (slurp "src/rcrly.lfe")
#(ok rcrly)
>
We’re going to need some helper functions:
> (defun get-xacts (acct)
(rcrly:get-linked '(account transactions) acct))
get-xacts
> (defun check-xacts (xacts)
(rcrly:map #'check-xact/1 xacts))
check-xacts
> (defun check-xact (xact)
(if (=/= (rcrly:get-in '(transaction recurring) xact) "true")
(if (=:= (rcrly:get-in '(transaction status) xact) "success")
(rcrly:get-in '(transaction uuid) xact))))
check-xact
> (defun id?
((id) (when (is_list id))
'true)
((x) x))
id?
>
Now we can perform our defined task:
> (->> (rcrly:get-accounts) ; this returns a multi-valued result
(rcrly:map #'get-xacts/1) ; this returns a list of multi-valued results
(lists:map #'check-xacts/1) ; this returns a list of lists
(lists:foldl #'++/2 '()) ; this flattns the list, preserving strings
(lists:filter #'id?/1)) ; just returns results that are ids
Results:
("2d9d1054c2716a3d38260146d28ebc7c"
"2dc20791440f9313a877414fe1a6f7a4"
"2dc2076ab55c2054cfaf3b427589437a"
"2dbc6c2d09c5aed53a9ede41138f63df"
"2dbc6c17524ca5cda869684a6bb7aae3")
Of the 12 transactions in the accounts this was tested against, those five satisfied the criteria of being non-recurring and in a successful state.
This section might be more accurately called “processing results through function composition” but that was a bit long. We hope you’ll forgive the poetic license we took!
With that said, here’s an example of a potential “data flow” using function composition to get the following:
- get a list of all the accounts
- for each account, get all of its transactions
- for each transaction, check to see that it’s not recurring
- return the transaction id for each recurring transation which has a “success” state
Batched Results and Paging
TBD
Relationships and Linked Data
In the Recurly REST API, data relationships are encoded in media links, per
common best REST practices. Linked data may be retreived easily using the
get-linked/2
utility function (analog to the get-in/2
function).
For more information, see the get-linked
section above.
Handling Errors
The Recurly API will return errors under various circumstances. For instance, an error is returned when attempting to look up billing information with a non-existent account:
> (set `#(error ,error) (rcrly:get-billing-info 'noaccountid))
Resulting error:
#(error
#(error ()
(#(symbol () ("not_found"))
#(description
(#(lang "en-US"))
("Couldn't find Account with account_code = noaccountid")))))
Note that we pattern-matched against
#(error ...)
. Also, you may use theget-in
function to extract error information:
> (rcrly:get-in '(error description) error)
"Couldn't find Account with account_code = noaccountid"
Any HTTP request that generates an HTTP status code equal to or greater than 400 will be converted to an error. For example, requesting account information with an id that no account has will generate a
404 - Not Found
which will be converted by rcrly to an application error:
> (set `#(error ,error) (rcrly:get-account 'noaccountid))
Resulting error:
#(error
#(error ()
(#(symbol () ("not_found"))
#(description
(#(lang "en-US"))
("Couldn't find Account with account_code = noaccountid")))))
Extracting the message:
> (rcrly:get-in '(error description) error)
"Couldn't find Account with account_code = noaccountid"
rcrly Errors: TBD
lhttpc Errors: TBD
As mentioned in the “Working with Results” section, all parsed responses from
Recurly are a tuple of either #(ok ...)
or #(error ...)
. All processing
of rcrly results should pattern match against these typles, handling the error
cases as appropriate for the application using the rcrly library.
There are four types of errors that rcrly aims to address:
- Errors in the Recurly service (e.g., making a bad request that the service can’t serve)
- General HTTP errors
- Errors in the rcrly library
- Errors in the underlying lhttpc library
Logging
rcrly uses the LFE logjam library for logging. The log level may be configured in two places:
- an
lfe.config
file (this is the standard location for logjam) - on a per-request basis in the
options
arguement to API calls
The default log level is emergency
, so you should never notice it’s there
(unless, of course, you have lots ot logging defined for the emergency
level …). The intended use for rcrly logging is on a per-request basis for
debugging purposes (though, of course, this may be easily overridden in your
application code by setting the log level you desire in the lfe.config
file).
Note that when passing the log-level
option in an API call, it sets the
log level for the logging service which is running in the background. As such,
the log-level
option does not need to be passed again until you wish to
change it. In other words, when passed as an option, it is set for all future
API calls.
For more details on logging per-request, see the “Options” section above.
The API
Each API call has a default arity and then an arity+1 where the “+1” is an argument for rcrly client options (see the “Options” section above).
For each of the API functions listed below, be sure to examine the linked Recurly documentation for information about payloads.
Accounts
Recurly Accounts documentation
get-accounts
Get all accounts:
> (set `#(ok ,accounts) (rcrly:get-accounts))
Results:
#(ok
(#(account ...)
#(account ...)))
> (length accounts)
2
Pages on all accounts in the system.
get-account
Takes a single arguement and returns data for the account associated with the provided id:
> (set `#(ok ,account) (rcrly:get-account 1))
Results:
#(ok
#(account
(#(adjustments ...)
...)))
> (rcrly:get-in '(account state) account)
"active"
> (rcrly:get-in '(account address city) account)
"Fairville"
Get a particular account by account ID.
create-account
To use from the REPL, first, pull in the XML macros:
> (slurp "src/rcrly-xml.lfe")
#(ok rcrly-xml)
Now create your payload:
> (set payload
(xml/account
(list
(xml/account_code "123")
(xml/email "alice@example.com")
(xml/first_name "Alice")
(xml/last_name "Guthrie"))))
Which will give you:
"<account>...</account>"
Now make the API call to create the account:
> (set `#(ok ,account) (rcrly:create-account payload))
#(ok
#(account ...))
With the account created, we can extract data from the results:
> (rcrly:get-in '(account email) account)
"alice@example.com"
Create an account based upon provided payload data.
update-account
Create your payload:
> (set payload
(xml/account
(xml/company_name "Alice's Hacker Cafe")))
"<account>...</account>"
Now make the API call to update the account:
> (set `#(ok ,account) (rcrly:update-account 123 payload))
#(ok
#(account ...))
With the account updated, we can extract data from the results:
> (rcrly:get-in '(account email) account)
"alice@example.com"
> (rcrly:get-in '(account company_name) account)
"Alice's Hacker Cafe"
This function takes account id and payload data.
close-account
Close an account:
> (set `#(ok "") (rcrly:close-account 123))
#(ok ())
Takes an account id.
reopen-account
Re-open an account that had been closed:
> (set `#(ok ,account) (rcrly:reopen-account 123))
#(ok
#(account ...))
Takes an account id.
Adjustments
Recurly Adjustments documentation
get-adjustments
Get all adjustments:
> (set `#(ok ,adjustments) (rcrly:get-adjustments 1))
Results:
#(ok
#(adjustments
(#(type "array"))
(#(adjustment ...)
#(adjustment ...)
...)))
Takes an account id.
get-adjustment
Get a particular adjustment:
> (set `#(ok ,adjustment) (rcrly:get-adjustment "2d97cfa52e80a675a532ba4e8ea25401"))
Results:
#(ok
#(adjustment
(#(type "credit")
#(href
"https://yourname.recurly.com/v2/adjustments/2d97cf12a5...."))
(#(account (#(href "https://yourname.recurly.com/v2/accounts/1")) ())
#(uuid () ("2d97cfa52e80a675a532ba4e8ea25401"))
#(state () ("pending"))
...
#(origin () ("credit"))
...
#(total_in_cents (#(type "integer")) ("-100"))
#(currency () ("USD"))
#(taxable (#(type "boolean")) ("false"))
#(start_date (#(type "datetime")) ("2015-03-17T18:34:56Z"))
#(end_date (#(nil "nil")) ())
#(created_at (#(type "datetime")) ("2015-03-17T18:34:56Z")))))
Now you can do the usual things:
> (rcrly:get-in '(adjustment total_in_cents) adjustment)
"-100"
> (rcrly:get-in '(adjustment state) adjustment)
"pending"
> (rcrly:get-in '(adjustment origin) adjustment)
"credit"
Takes a UUID.
Billing Info
Recurly Billing Info documentation
get-billing-info
Get billing info for a particular account:
> (set `#(ok ,info) (rcrly:get-billing-info 1))
Results:
#(ok
#(billing_info
(#(type "credit_card")
#(href "https://yourname.recurly.com/v2/accounts/1/billing_info"))
(#(account (#(href "https://yourname.recurly.com/v2/accounts/1")) ())
...
#(company (#(nil "nil")) ())
#(address1 () ("108 Main St"))
...
#(city () ("Fairville"))
#(state () ("WI"))
#(zip () ("12345"))
...
#(card_type () ("Visa"))
#(year (#(type "integer")) ("2016"))
#(month (#(type "integer")) ("3"))
...)))
Extract some data:
> (rcrly:get-in '(billing_info card_type) info)
"Visa"
Takes an account id.
update-billing-info
To update billing info from the REPL, first pull in the XML macros:
> (slurp "src/rcrly-xml.lfe")
#(ok rcrly-xml)
Now set some argument values (helps to keep things more readable):
> (set account-id 1)
1
> (set payload
(xml/billing_info
(list (xml/first_name "Verena")
(xml/last_name "Example"))))
"<billing_info> ... </billing_info>"
With those in place, you can made your API call to update the billing info:
> (set `#(ok ,info) (rcrly:update-billing-info account-id payload))
Results:
#(ok
#(billing_info
(#(type "credit_card")
#(href "https://yourname.recurly.com/v2/accounts/1/billing_info"))
(#(account (#(href "https://yourname.recurly.com/v2/accounts/1")) ())
...
#(company (#(nil "nil")) ())
#(address1 () ("108 Main St"))
...
#(city () ("Fairville"))
#(state () ("WI"))
#(zip () ("12345"))
...
#(card_type () ("Visa"))
#(year (#(type "integer")) ("2016"))
#(month (#(type "integer")) ("3"))
...)))
We can easily confirm that our results have the updated data:
> (rcrly:get-in '(billing_info first_name) info)
"Verena"
Takes payload data.
clear-billing-info
TBD
Invoices
Recurly Invoices documentation
get-all-invoices
Get all the invoices for all accounts:
> (set `#(ok ,invoices) (rcrly:get-all-invoices))
#(ok
#(invoices ...))
Takes no arguments.
get-invoices
Get all the invoices for one account:
> (set `#(ok ,invoices) (rcrly:get-invoices 123))
Results:
#(ok
#(invoices ...))
Takes an account id.
get-invoice
Get a particular invoice:
> (set `#(ok ,invoice) (rcrly:get-invoice 1402))
Result has the follwoing form:
#(ok
#(invoice ...))
Takes an invoice number:
get-invoice-pdf
TBD
preview-invoice
TBD
invoice
TBD
set-paid-invoice
TBD
set-failed-invoice
TBD
set-line-refund-invoice
TBD
set-open-refund-invoice
TBD
Plans
Recurly Invoices documentation
get-plans
Get all plans:
> (set `#(ok ,plans) (rcrly:get-plans))
Results:
#(ok
#(plans
(#(type "array"))
(#(plan ...))))
Takes no arguments.
get-plan
Get a specific plan:
> (set `#(ok ,plan) (rcrly:get-plan 1402))
#(ok
#(plan ...))
Extract the plan name:
> (rcrly:get-in '(plan name) plan)
"30-Day Free Trial"
Takes a plan code.
create-plan
Create your payload:
> (set payload
(xml/plan
(list
(xml/plan_code "gold")
(xml/name "Gold plan")
(xml/setup_fee_in_cents
(list
(xml/USD "1000")
(xml/EUR "800")))
(xml/unit_amount_in_cents
(list
(xml/USD "6000")
(xml/EUR "4500")))
(xml/plan_interval_length "1")
(xml/plan_interval_unit "months")
(xml/tax_exempt "false"))))
"<plan>...</plan>"
Now make the API call to create the plan:
> (set `#(ok ,plan) (rcrly:create-plan payload))
#(ok
#(plan ...))
With the plan created, we can extract data from the results:
> (rcrly:get-in '(plan setup_fee_in_cents EUR) plan)
"4500"
Takes payload data.
update-plan
TBD
delete-plan
To delete a plan, simply pass the plan code to
delete-plan
:
> (set `#(ok ,results) (rcrly:delete-plan "gold"))
[response TBD]
Subscriptions
Recurly InvoicesSubscriptions documentation
get-all-subscriptions
Get all subscriptions:
> (set `#(ok ,subs) (rcrly:get-all-subscriptions '()))
#(ok
#(subscriptions
(#(type "array"))
(#(subscription ...))))
Takes no arguments.
get-subscriptions
Get all subscriptions for an account:
> (set `#(ok ,subs) (rcrly:get-subscriptions 123))
#(ok
#(subscriptions
(#(type "array"))
(#(subscription ...))))
Takes an account id:
get-subscription
Get a particular subscription:
> (set uuid "2dbc6c2cb823174353853a409c90d419")
"2dbc6c2cb823174353853a409c90d419"
> (set `#(ok ,subs) (rcrly:get-subscription uuid))
#(ok
#(subscription ...))
Extract data as needed:
> (rcrly:get-in '(subscription current_period_started_at) subs)
"2015-03-24T21:12:14Z"
Takes a subscription UUID:
create-subscription
To create a subscription, first create your payload:
> (set payload
(xml/subscription
(list
(xml/plan_code "gold")
(xml/currency "USD")
(xml/account
(xml/account_code "123")))))
"<subscription>...</subscription>"
Now make the API call to create the plan:
> (set `#(ok ,subs) (rcrly:create-subscription payload))
#(ok
#(subscription ...))
With the plan created, we can extract data from the results:
> (rcrly:get-in '(subscription plan name) subs)
"Gold plan"
Takes payload data.
preview-subscription
TBD
update-subscription
To update a subscription, create your payload:
> (set uuid "2dbc6c2cb823174353853a409c90d419")
"2dbc6c2cb823174353853a409c90d419"
> (set payload
(xml/subscription
(list
(xml/timeframe "now")
(xml/plan_code "silver"))))
"<subscription>...</subscription>"
Now make the API call to create the plan:
> (set `#(ok ,subs) (rcrly:update-subscription uuid payload))
#(ok
#(subscription ...))
With the plan created, we can extract data from the results:
> (rcrly:get-in '(subscription plan name) subs)
"Silver plan"
Takes subscription UUID and payload data.
update-subscription-notes
TBD
cancel-subscription
To cancel a subscription:
> (set uuid "2dbc6c2cb823174353853a409c90d419")
"2dbc6c2cb823174353853a409c90d419"
> (set #(ok ,subs) (rcrly:cancel-subscription uuid))
#(ok
#(subscription ...))
And you can check the subscription state:
> (rcrly:get-in '(subscription state) subs)
"canceled"
Takes a subscription UUID.
reactivate-subscription
To reactivate a subscription:
> (set uuid "2dbc6c2cb823174353853a409c90d419")
"2dbc6c2cb823174353853a409c90d419"
> (set #(ok ,subs) (rcrly:reactivate-subscription uuid))
#(ok
#(subscription ...))
Check the subscription state:
> (rcrly:get-in '(subscription state) subs)
"active"
Takes a subscription UUID
terminate-subscription
To terminate a subscription:
> (set uuid "2dbc6c2cb823174353853a409c90d419")
"2dbc6c2cb823174353853a409c90d419"
> (set #(ok ,subs) (rcrly:terminate-subscription uuid))
#(ok
#(subscription ...))
Check the subscription state:
> (rcrly:get-in '(subscription statue) subs)
"expired"
Takes a subscription UUID
postpone-subscription
Transactions
Recurly Transactions documentation
get-all-transactions
TBD
get-transactions
TBD