diff --git a/recurly/__init__.py b/recurly/__init__.py index 9e91faad..f15e963e 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.10' +API_VERSION = '2.11' """The API version to use when making API requests.""" CA_CERTS_FILE = None @@ -147,7 +147,9 @@ class Account(Resource): 'has_active_subscription', 'has_future_subscription', 'has_canceled_subscription', + 'has_paused_subscription', 'has_past_due_invoice', + 'preferred_locale', ) _classes_for_nodename = { 'address': Address } @@ -647,9 +649,12 @@ class Adjustment(Resource): 'updated_at', 'type', 'revenue_schedule_type', + 'shipping_address', + 'shipping_address_id', ) xml_attribute_attributes = ('type',) - _classes_for_nodename = {'tax_detail': TaxDetail,} + _classes_for_nodename = {'tax_detail': TaxDetail, 'shipping_address': + ShippingAddress} # This can be removed when the `original_adjustment_uuid` is moved to a link def __getattr__(self, name): @@ -866,6 +871,7 @@ class Purchase(Resource): 'customer_notes', 'terms_and_conditions', 'vat_reverse_charge_notes', + 'shipping_address_id', ) def invoice(self): @@ -983,6 +989,8 @@ class Subscription(Resource): 'converted_at', 'no_billing_info_reason', 'imported_trial', + 'remaining_pause_cycles', + 'paused_at', ) sensitive_attributes = ('number', 'verification_value', 'bulk') @@ -1001,6 +1009,27 @@ def update_notes(self, **kwargs): url = urljoin(self._url, '%s/notes' % self.uuid) self.put(url) + def pause(self, remaining_pause_cycles): + """Pause a subscription""" + url = urljoin(self._url, '%s/pause' % self.uuid) + elem = ElementTree.Element(self.nodename) + elem.append(Resource.element_for_value('remaining_pause_cycles', + remaining_pause_cycles)) + body = ElementTree.tostring(elem, encoding='UTF-8') + + response = self.http_request(url, 'PUT', body, { 'Content-Type': + 'application/xml; charset=utf-8' }) + + if response.status not in (200, 201, 204): + self.raise_http_error(response) + + self.update_from_element(ElementTree.fromstring(response.read())) + + def resume(self): + """Resume a subscription""" + url = urljoin(self._url, '%s/resume' % self.uuid) + self.put(url) + def _update(self): if not hasattr(self, 'timeframe'): self.timeframe = 'now' diff --git a/tests/fixtures/account/created.xml b/tests/fixtures/account/created.xml index 8d836ead..2188f2c5 100644 --- a/tests/fixtures/account/created.xml +++ b/tests/fixtures/account/created.xml @@ -9,6 +9,7 @@ Content-Type: application/xml; charset=utf-8 testmock 444444-UK + en-US  HTTP/1.1 201 Created @@ -41,4 +42,5 @@ Location: https://api.recurly.com/v2/accounts/testmock true + en-US diff --git a/tests/fixtures/purchase/authorized.xml b/tests/fixtures/purchase/authorized.xml index 40955b82..1b321d70 100644 --- a/tests/fixtures/purchase/authorized.xml +++ b/tests/fixtures/purchase/authorized.xml @@ -10,6 +10,18 @@ Content-Type: application/xml; charset=utf-8 testmock benjamin.dumonde@example.com + + + 123 Main St + New Orleans + US + Verena + Example + Work + LA + 70114 + + Verena Example diff --git a/tests/fixtures/purchase/invoiced.xml b/tests/fixtures/purchase/invoiced.xml index ae64b31d..0837e6cb 100644 --- a/tests/fixtures/purchase/invoiced.xml +++ b/tests/fixtures/purchase/invoiced.xml @@ -9,6 +9,18 @@ Content-Type: application/xml; charset=utf-8 testmock + + + 123 Main St + New Orleans + US + Verena + Example + Work + LA + 70114 + + Verena Example @@ -105,6 +117,16 @@ Location: https://api.recurly.com/v2/invoices/1021 2017-10-06T21:25:56Z 2017-10-06T21:25:56Z evenly + + 123 Main St + + New Orleans + LA + 70114 + US + Work + + @@ -127,6 +149,16 @@ Location: https://api.recurly.com/v2/invoices/1021 2017-10-06T21:25:56Z 2017-10-06T21:25:56Z + + 123 Main St + + New Orleans + LA + 70114 + US + Work + + @@ -149,6 +181,16 @@ Location: https://api.recurly.com/v2/invoices/1021 2017-10-06T21:25:56Z 2017-10-06T21:25:56Z + + 123 Main St + + New Orleans + LA + 70114 + US + Work + + diff --git a/tests/fixtures/purchase/previewed.xml b/tests/fixtures/purchase/previewed.xml index e8bdfec1..def32c4e 100644 --- a/tests/fixtures/purchase/previewed.xml +++ b/tests/fixtures/purchase/previewed.xml @@ -9,6 +9,18 @@ Content-Type: application/xml; charset=utf-8 testmock + + + 123 Main St + New Orleans + US + Verena + Example + Work + LA + 70114 + + Verena Example diff --git a/tests/fixtures/subscription/pause.xml b/tests/fixtures/subscription/pause.xml new file mode 100644 index 00000000..f0a481ab --- /dev/null +++ b/tests/fixtures/subscription/pause.xml @@ -0,0 +1,55 @@ +PUT https://api.recurly.com/v2/subscriptions/123456789012345678901234567890ab/pause 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 + + + + 1 + + +HTTP/1.1 200 OK +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/subscriptions/123456789012345678901234567890ab + + + + + 123456789012345678901234567890ab + + + basicplan + Basic Plan + + active + 1 + EUR + 1000 + 2011-05-27T07:00:00Z + + + 2011-06-27T07:00:00Z + 2010-07-27T07:00:00Z + + + 0 + usst + plan_free_trial + 2019-07-27T07:00:00Z + 1> + + + usage + + + marketing_emails + 5 + 1 + price + + + + diff --git a/tests/fixtures/subscription/resume.xml b/tests/fixtures/subscription/resume.xml new file mode 100644 index 00000000..40785e19 --- /dev/null +++ b/tests/fixtures/subscription/resume.xml @@ -0,0 +1,52 @@ +PUT https://api.recurly.com/v2/subscriptions/123456789012345678901234567890ab/resume 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 + + + +HTTP/1.1 200 OK +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/subscriptions/123456789012345678901234567890ab + + + + + 123456789012345678901234567890ab + + + basicplan + Basic Plan + + active + 1 + EUR + 1000 + 2011-05-27T07:00:00Z + + + 2011-06-27T07:00:00Z + 2010-07-27T07:00:00Z + + + 0 + usst + plan_free_trial + 2019-07-27T07:00:00Z + 1> + + + usage + + + marketing_emails + 5 + 1 + price + + + + diff --git a/tests/test_resources.py b/tests/test_resources.py index 02bd89fd..95b3c477 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -67,6 +67,18 @@ def test_purchase(self): currency = 'USD', account = Account( account_code = account_code, + shipping_addresses = [ + ShippingAddress( + first_name = 'Verena', + last_name = 'Example', + address1 = '123 Main St', + city = 'New Orleans', + state = 'LA', + zip = '70114', + country = 'US', + nickname = 'Work' + ) + ], billing_info = BillingInfo( first_name = 'Verena', last_name = 'Example', @@ -97,6 +109,8 @@ def test_purchase(self): self.assertIsInstance(collection.charge_invoice, Invoice) self.assertIsInstance(collection.credit_invoices, list) self.assertIsInstance(collection.credit_invoices[0], Invoice) + self.assertIsInstance(collection.charge_invoice.line_items[0].shipping_address, + ShippingAddress) with self.mock_request('purchase/previewed.xml'): collection = purchase.preview() self.assertIsInstance(collection, InvoiceCollection) @@ -115,6 +129,7 @@ def test_account(self): account = Account(account_code=account_code) account.vat_number = '444444-UK' + account.preferred_locale = 'en-US' with self.mock_request('account/created.xml'): account.save() self.assertEqual(account._url, urljoin(recurly.base_uri(), 'accounts/%s' % account_code)) @@ -122,6 +137,7 @@ def test_account(self): self.assertEqual(account.vat_location_enabled, True) self.assertEqual(account.cc_emails, 'test1@example.com,test2@example.com') + self.assertEqual(account.preferred_locale, 'en-US') with self.mock_request('account/list-active.xml'): active = Account.all_active() @@ -1172,6 +1188,19 @@ def test_measured_unit(self): self.assertEqual(measured_unit.description, 'Unit of Marketing Email') self.assertEqual(measured_unit.id, 123456) + def test_subscription_pause_resume(self): + with self.mock_request('subscription/show.xml'): + sub = Subscription.get('123456789012345678901234567890ab') + + with self.mock_request('subscription/pause.xml'): + sub.pause(1) + + self.assertIsInstance(sub.paused_at, datetime) + self.assertEqual(sub.remaining_pause_cycles, 1) + + with self.mock_request('subscription/resume.xml'): + sub.resume() + def test_usage(self): usage = Usage() usage.amount = 100 # record 100 emails