diff --git a/recurly/__init__.py b/recurly/__init__.py
index d01aecc3..e16ebe97 100644
--- a/recurly/__init__.py
+++ b/recurly/__init__.py
@@ -41,7 +41,7 @@
API_KEY = None
"""The API key to use when authenticating API requests."""
-API_VERSION = '2.8'
+API_VERSION = '2.9'
"""The API version to use when making API requests."""
CA_CERTS_FILE = None
@@ -365,6 +365,7 @@ class BillingInfo(Resource):
'account_number',
'currency',
'updated_at',
+ 'external_hpp_type',
)
sensitive_attributes = ('number', 'verification_value', 'account_number')
xml_attribute_attributes = ('type',)
@@ -853,6 +854,20 @@ def preview(self):
url = urljoin(recurly.base_uri(), self.collection_path + '/preview')
return self.__invoice(url)
+ def authorize(self):
+ """
+ Will generate an authorized invoice for the purchase. Runs validations
+ but does not run any transactions. This endpoint will create a
+ pending purchase that can be activated at a later time once payment
+ has been completed on an external source (e.g. Adyen's Hosted
+ Payment Pages).
+
+ Returns:
+ Invoice: The authorized invoice
+ """
+ url = recurly.base_uri() + self.collection_path + '/authorize'
+ return self.__invoice(url)
+
def __invoice(self, url):
# We must null out currency in subscriptions and adjustments
# TODO we should deprecate and remove default currency support
diff --git a/recurly/resource.py b/recurly/resource.py
index b0d214ae..23e404db 100644
--- a/recurly/resource.py
+++ b/recurly/resource.py
@@ -14,6 +14,13 @@
from six.moves import http_client
from six.moves.urllib.parse import urlencode, urlsplit, quote
+def urlencode_params(args):
+ # Need to make bools lowercase
+ for k, v in six.iteritems(args):
+ if isinstance(v, bool):
+ args[k] = str(v).lower()
+ return urlencode(args)
+
class Money(object):
"""An amount of money in one or more currencies."""
@@ -514,7 +521,7 @@ def update_from_element(self, elem):
def _make_actionator(self, url, method, extra_handler=None):
def actionator(*args, **kwargs):
if kwargs:
- full_url = '%s?%s' % (url, urlencode(kwargs))
+ full_url = '%s?%s' % (url, urlencode_params(kwargs))
else:
full_url = url
@@ -574,7 +581,7 @@ def __getattr__(self, name):
def make_relatitator(url):
def relatitator(**kwargs):
if kwargs:
- full_url = '%s?%s' % (url, urlencode(kwargs))
+ full_url = '%s?%s' % (url, urlencode_params(kwargs))
else:
full_url = url
@@ -608,7 +615,7 @@ def all(cls, **kwargs):
"""
url = recurly.base_uri() + cls.collection_path
if kwargs:
- url = '%s?%s' % (url, urlencode(kwargs))
+ url = '%s?%s' % (url, urlencode_params(kwargs))
return Page.page_for_url(url)
@classmethod
@@ -618,7 +625,7 @@ def count(cls, **kwargs):
"""
url = recurly.base_uri() + cls.collection_path
if kwargs:
- url = '%s?%s' % (url, urlencode(kwargs))
+ url = '%s?%s' % (url, urlencode_params(kwargs))
return Page.count_for_url(url)
def save(self):
diff --git a/tests/fixtures/purchase/authorized.xml b/tests/fixtures/purchase/authorized.xml
new file mode 100644
index 00000000..86391a74
--- /dev/null
+++ b/tests/fixtures/purchase/authorized.xml
@@ -0,0 +1,152 @@
+POST https://api.recurly.com/v2/purchases/authorize HTTP/1.1
+X-Api-Version: {api-version}
+Accept: application/xml
+Authorization: Basic YXBpa2V5Og==
+User-Agent: {user-agent}
+Content-Type: application/xml; charset=utf-8
+
+
+
+
+ testmock
+ benjamin.dumonde@example.com
+
+ Verena
+ Example
+ 4111-1111-1111-1111
+ 123
+ 2020
+ 11
+ 123 Main St
+ New Orleans
+ LA
+ 70114
+ US
+ USD
+ adyen
+
+
+
+
+ Item 1
+ 1
+ 1000
+
+
+ Item 2
+ 2
+ 2000
+
+
+ USD
+
+
+ gold
+
+
+
+
+HTTP/1.1 200 OK
+Content-Type: application/xml; charset=utf-8
+Location: https://api.recurly.com/v2/invoices/1021
+
+
+
+
+
+ 123 Main St
+
+ New Orleans
+ LA
+ 70114
+ US
+
+
+ 40625fd700c1c2d90060744092b4a1b1
+ open
+
+
+
+
+ 6000
+ 0
+ 6000
+ 6000
+ USD
+
+
+ 2017-10-06T21:25:55Z
+
+
+
+
+ 0
+ automatic
+
+
+
+ 40625fd6fc46181e2f15044db38c1c82
+ pending
+ gold
+
+ gold
+ plan
+ 1000
+ 1
+ 0
+ 0
+ 1000
+ USD
+ false
+ 2017-10-06T21:25:55Z
+ 2017-11-06T21:25:55Z
+
+
+ evenly
+
+
+
+ 40625fd6e6a5817b59a2174b72a92fea
+ pending
+ Item 1
+
+
+ debit
+ 1000
+ 1
+ 0
+ 0
+ 1000
+ USD
+ false
+ 2017-10-06T21:25:55Z
+
+
+
+
+
+
+
+ 40625fd6e9096c9922b52646e29f6d5e
+ pending
+ Item 2
+
+
+ debit
+ 2000
+ 2
+ 0
+ 0
+ 4000
+ USD
+ false
+ 2017-10-06T21:25:55Z
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_resources.py b/tests/test_resources.py
index 42a5df19..e654c975 100644
--- a/tests/test_resources.py
+++ b/tests/test_resources.py
@@ -88,6 +88,11 @@ def test_purchase(self):
with self.mock_request('purchase/previewed.xml'):
preview_invoice = purchase.preview()
self.assertIsInstance(preview_invoice, Invoice)
+ with self.mock_request('purchase/authorized.xml'):
+ purchase.account.email = 'benjamin.dumonde@example.com'
+ purchase.account.billing_info.external_hpp_type = 'adyen'
+ authorized_invoice = purchase.authorize()
+ self.assertIsInstance(authorized_invoice, Invoice)
def test_account(self):
account_code = 'test%s' % self.test_id