diff --git a/recurly/__init__.py b/recurly/__init__.py index 34a9234e..a35ec194 100644 --- a/recurly/__init__.py +++ b/recurly/__init__.py @@ -127,6 +127,27 @@ class AccountAcquisition(Resource): 'updated_at', ) +class CustomField(Resource): + + """A field to store extra data on the account or subscription.""" + + nodename = 'custom_field' + + attributes = ( + 'name', + 'value', + ) + + def to_element(self, root_name=None): + # Include the field name/value pair when the value changed + if 'value' in self.__dict__: + try: + self.name = self.name # forces name into __dict__ + except AttributeError: + pass + + return super(CustomField, self).to_element(root_name) + class Account(Resource): """A customer account.""" @@ -160,9 +181,10 @@ class Account(Resource): 'has_paused_subscription', 'has_past_due_invoice', 'preferred_locale', + 'custom_fields', ) - _classes_for_nodename = { 'address': Address } + _classes_for_nodename = { 'address': Address, 'custom_field': CustomField } sensitive_attributes = ('number', 'verification_value',) @@ -1016,6 +1038,7 @@ class Subscription(Resource): 'next_bill_date', 'current_term_started_at', 'current_term_ends_at', + 'custom_fields', ) sensitive_attributes = ('number', 'verification_value', 'bulk') diff --git a/tests/fixtures/account/created-with-custom-fields.xml b/tests/fixtures/account/created-with-custom-fields.xml new file mode 100644 index 00000000..43fa4a8c --- /dev/null +++ b/tests/fixtures/account/created-with-custom-fields.xml @@ -0,0 +1,45 @@ +POST https://api.recurly.com/v2/accounts 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 + + + field_1 + my field value + + + + +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/accounts/testmock + + + + + + + + + + testmock + + + + + + + + + + field_1 + my field value + + + diff --git a/tests/fixtures/account/exists-custom-fields.xml b/tests/fixtures/account/exists-custom-fields.xml new file mode 100644 index 00000000..3dd598d5 --- /dev/null +++ b/tests/fixtures/account/exists-custom-fields.xml @@ -0,0 +1,37 @@ +GET https://api.recurly.com/v2/accounts/testmock HTTP/1.1 +X-Api-Version: {api-version} +Accept: application/xml +Authorization: Basic YXBpa2V5Og== +User-Agent: {user-agent} + + +HTTP/1.1 200 OK +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + testmock + + + + + + I + + + + field1 + original value1 + + + field2 + original value2 + + + diff --git a/tests/fixtures/account/updated-custom-fields.xml b/tests/fixtures/account/updated-custom-fields.xml new file mode 100644 index 00000000..5c5e9856 --- /dev/null +++ b/tests/fixtures/account/updated-custom-fields.xml @@ -0,0 +1,37 @@ +PUT https://api.recurly.com/v2/accounts/testmock 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 + + + + + + + field2 + new value2 + + + testmock + + +HTTP/1.1 200 OK +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/accounts/testmock + + + + testmock + + + field1 + original value1 + + + field2 + new value2 + + + diff --git a/tests/fixtures/subscription/subscribe-custom-fields.xml b/tests/fixtures/subscription/subscribe-custom-fields.xml new file mode 100644 index 00000000..aedba930 --- /dev/null +++ b/tests/fixtures/subscription/subscribe-custom-fields.xml @@ -0,0 +1,75 @@ +POST https://api.recurly.com/v2/subscriptions 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 + + + + basicplan + USD + + subscribe-mock-2 + + + my_account_field + here is the account value you seek + + + + Verena + Example + 4111 1111 1111 1111 + 7777 + 2015 + 12 + 123 Main St + San José + CA + 94105 + US + USD + + + + + my_sub_field + definitely sub value + + + + +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/plans/basicplan + + + + 123456789012345678901234567890ab + + + basicplan + Basic Plan + + active + 1 + EUR + 1000 + 2011-05-27T07:00:00Z + + + 2011-06-27T07:00:00Z + 2010-07-27T07:00:00Z + + + + + + + my_sub_field + definitely sub value + + + diff --git a/tests/test_resources.py b/tests/test_resources.py index 510239f0..c1bbcb23 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -11,7 +11,7 @@ from six.moves import urllib, http_client from six.moves.urllib.parse import urljoin -from recurly import Account, AddOn, Address, Adjustment, BillingInfo, Coupon, Plan, Redemption, Subscription, SubscriptionAddOn, Transaction, MeasuredUnit, Usage, GiftCard, Delivery, ShippingAddress, AccountAcquisition, Purchase, Invoice, InvoiceCollection, CreditPayment +from recurly import Account, AddOn, Address, Adjustment, BillingInfo, Coupon, Plan, Redemption, Subscription, SubscriptionAddOn, Transaction, MeasuredUnit, Usage, GiftCard, Delivery, ShippingAddress, AccountAcquisition, Purchase, Invoice, InvoiceCollection, CreditPayment, CustomField from recurly import Money, NotFoundError, ValidationError, BadRequestError, PageError from recurlytests import RecurlyTest, xml @@ -230,6 +230,13 @@ def test_account(self): with self.mock_request('account/numeric-deleted.xml'): account.delete() + """Get taxed account""" + with self.mock_request('account/show-taxed.xml'): + account = Account.get(account_code) + self.assertTrue(account.tax_exempt) + + def test_account_addresses(self): + account_code = 'test%s' % self.test_id """Create an account with an account level address""" account = Account(account_code=account_code) account.address.address1 = '123 Main St' @@ -276,10 +283,34 @@ def test_account(self): with self.mock_request('shipping_addresses/created-on-existing-account.xml'): shipping_address = account.create_shipping_address(shipping_address) - """Get taxed account""" - with self.mock_request('account/show-taxed.xml'): - account = Account.get(account_code) - self.assertTrue(account.tax_exempt) + def test_account_custom_fields(self): + account_code = 'test%s' % self.test_id + """Create an account with a custom field""" + account = Account( + account_code=account_code, + custom_fields=[ + CustomField(name="field_1", value="my field value") + ] + ) + with self.mock_request('account/created-with-custom-fields.xml'): + account.save() + + self.assertEquals(account.custom_fields[0].name, 'field_1') + self.assertEquals(account.custom_fields[0].value, 'my field value') + + """Update custom fields on an account""" + with self.mock_request('account/exists-custom-fields.xml'): + existing_account = Account.get(account_code) + fields = existing_account.custom_fields + fields[1].value = "new value2" + existing_account.custom_fields = fields + with self.mock_request('account/updated-custom-fields.xml'): + existing_account.save() + + self.assertEquals(existing_account.custom_fields[0].name, 'field1') + self.assertEquals(existing_account.custom_fields[0].value, 'original value1') + self.assertEquals(existing_account.custom_fields[1].name, 'field2') + self.assertEquals(existing_account.custom_fields[1].value, 'new value2') def test_account_acquisition(self): account_code = 'test%s' % self.test_id @@ -1320,6 +1351,39 @@ def test_subscribe_add_on(self): with self.mock_request('subscribe-add-on/plan-deleted.xml'): plan.delete() + def test_subscription_custom_fields(self): + account_code = 'subscribe-%s-2' % self.test_id + sub = Subscription( + plan_code='basicplan', + currency='USD', + account=Account( + account_code=account_code, + billing_info=BillingInfo( + first_name='Verena', + last_name='Example', + address1='123 Main St', + city=six.u('San Jos\xe9'), + state='CA', + zip='94105', + country='US', + type='credit_card', + number='4111 1111 1111 1111', + verification_value='7777', + year='2015', + month='12', + ), + custom_fields=[CustomField(name='my_account_field', value='here is the account value you seek')], + ), + custom_fields=[CustomField(name='my_sub_field', value='definitely sub value')], + ) + + with self.mock_request('subscription/subscribe-custom-fields.xml'): + sub.save() + + self.assertTrue(sub._url) + self.assertEquals(sub.custom_fields[0].name, 'my_sub_field') + self.assertEquals(sub.custom_fields[0].value, 'definitely sub value') + def test_account_notes(self): account1 = Account(account_code='note%s' % self.test_id) account2 = Account(account_code='note%s' % self.test_id)