diff --git a/recurly/__init__.py b/recurly/__init__.py index 80811564..9c1e4325 100644 --- a/recurly/__init__.py +++ b/recurly/__init__.py @@ -1649,6 +1649,20 @@ def refund(self, **kwargs): Transaction._classes_for_nodename['transaction'] = Transaction +class PlanRampInterval(Resource): + """A plan ramp + representing a price point and the billing_cycle to begin that price point + """ + + nodename = 'ramp_interval' + collection_path = 'ramp_intervals' + + attributes = { + 'starting_billing_cycle', + 'unit_amount_in_cents' + } + + class Plan(Resource): """A service level for your service to which a customer account @@ -1690,8 +1704,12 @@ class Plan(Resource): 'auto_renew', 'allow_any_item_on_subscriptions', 'dunning_campaign_id', + 'pricing_model', + 'ramp_intervals', ) + _classes_for_nodename = {'ramp_interval': PlanRampInterval } +# def get_add_on(self, add_on_code): """Return the `AddOn` for this plan with the given add-on code.""" url = urljoin(self._url, '/add_ons/%s' % (add_on_code,)) diff --git a/recurly/resource.py b/recurly/resource.py index 295b1e2c..8ce4fad5 100644 --- a/recurly/resource.py +++ b/recurly/resource.py @@ -445,11 +445,34 @@ def value_for_element(cls, elem): return value_class.from_element(elem) # Untyped complex elements should still be resource instances. Guess from the nodename. - if len(elem): # has children + if len(elem) == 1: value_class = cls._subclass_for_nodename(elem.tag) log.debug("Converting %r tag into a %s", elem.tag, value_class.__name__) return value_class.from_element(elem) + # Tries to deserialize arrays of elements without type 'array' definition or resource + if len(elem) > 1: + first_tag = elem[0].tag + last_tag = elem[-1].tag + # Check if the element have and array of items + # + # ... + # ... + # ... + # + if(first_tag == last_tag): + return [cls._subclass_for_nodename(sub_elem.tag).from_element(sub_elem) for sub_elem in elem] + # De-serialize one resource + # + # name + # description + # other text + # + else: + value_class = cls._subclass_for_nodename(elem.tag) + log.debug("Converting %r tag into a %s", elem.tag, value_class.__name__) + return value_class.from_element(elem) + value = elem.text or '' return value.strip() @@ -606,7 +629,7 @@ def relatitator(**kwargs): url = elem.attrib['href'] # has no url or has children - if url is '' or len(elem) > 0: + if url == '' or len(elem) > 0: return self.value_for_element(elem) else: return make_relatitator(url) diff --git a/tests/fixtures/plan/created_with_ramps.xml b/tests/fixtures/plan/created_with_ramps.xml new file mode 100644 index 00000000..4109da4a --- /dev/null +++ b/tests/fixtures/plan/created_with_ramps.xml @@ -0,0 +1,75 @@ +POST https://api.recurly.com/v2/plans 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 + + + + planmock + Mock Plan + + 200 + + 10 + ramp + + + + 2000 + + 1 + + + + 3000 + + 2 + + + + +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/plans/planmock + + + + + planmock + Mock Plan + + + + false + false + false + false + unit + + 1 + months + 0 + days + 10 + 2011-10-03T22:23:12Z + ramp + + 200 + + + + 1 + + 2000 + + + + 2 + + 3000 + + + + diff --git a/tests/fixtures/plan/exists_with_ramps.xml b/tests/fixtures/plan/exists_with_ramps.xml new file mode 100644 index 00000000..11ff4aee --- /dev/null +++ b/tests/fixtures/plan/exists_with_ramps.xml @@ -0,0 +1,52 @@ +GET https://api.recurly.com/v2/plans/planmock 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 + + + + + planmock + Mock Plan + + + + false + false + false + false + unit + + 1 + months + 0 + days + 10 + 2011-10-03T22:23:12Z + false + + + ramp + + 200 + + + + 1 + + 2000 + + + + 2 + + 3000 + + + + diff --git a/tests/fixtures/plan/updated_with_ramps.xml b/tests/fixtures/plan/updated_with_ramps.xml new file mode 100644 index 00000000..6754ca67 --- /dev/null +++ b/tests/fixtures/plan/updated_with_ramps.xml @@ -0,0 +1,66 @@ +PUT https://api.recurly.com/v2/plans/planmock 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 + + + + + + + 3000 + + 1 + + + + 4000 + + 2 + + + + +HTTP/1.1 200 OK +Content-Type: application/xml; charset=utf-8 + + + + + planmock + Mock Plan + + + + false + false + false + false + unit + + 2 + months + 0 + days + Setup Fee AC + 2011-10-03T22:23:12Z + + 200 + + + + 1 + + 3000 + + + + 2 + + 4000 + + + + diff --git a/tests/test_resources.py b/tests/test_resources.py index d6033550..fd625ec4 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -9,7 +9,7 @@ from recurly import Account, AddOn, Address, Adjustment, BillingInfo, Coupon, Item, Plan, Redemption, Subscription, \ SubscriptionAddOn, Transaction, MeasuredUnit, Usage, GiftCard, Delivery, ShippingAddress, AccountAcquisition, \ Purchase, Invoice, InvoiceCollection, CreditPayment, CustomField, ExportDate, ExportDateFile, DunningCampaign, \ - DunningCycle, InvoiceTemplate + DunningCycle, InvoiceTemplate, PlanRampInterval from recurly import Money, NotFoundError, ValidationError, BadRequestError, PageError from recurly import recurly_logging as logging from recurlytests import RecurlyTest @@ -1471,6 +1471,57 @@ def test_plan(self): plan = Plan.get(plan_code) self.assertTrue(plan.tax_exempt) + def test_plan_with_ramps(self): + plan_code = 'plan%s' % self.test_id + with self.mock_request('plan/does-not-exist.xml'): + self.assertRaises(NotFoundError, Plan.get, plan_code) + + ramp_interval_1 = PlanRampInterval( + unit_amount_in_cents=Money(USD=2000), + starting_billing_cycle=1, + ) + ramp_interval_2 = PlanRampInterval( + unit_amount_in_cents=Money(USD=3000), + starting_billing_cycle=2, + ) + ramp_intervals = [ramp_interval_1, ramp_interval_2] + + plan = Plan( + plan_code=plan_code, + name='Mock Plan', + setup_fee_in_cents=Money(200), + pricing_model='ramp', + ramp_intervals=ramp_intervals, + total_billing_cycles=10 + ) + with self.mock_request('plan/created_with_ramps.xml'): + plan.save() + + self.assertEqual(plan.plan_code, plan_code) + self.assertEqual(len(plan.ramp_intervals), len(ramp_intervals)) + self.assertEqual(plan.pricing_model, 'ramp') + + self.assertEqual(plan.plan_code, plan_code) + + with self.mock_request('plan/exists_with_ramps.xml'): + same_plan = Plan.get(plan_code) + self.assertEqual(same_plan.plan_code, plan_code) + self.assertEqual(len(plan.ramp_intervals), len(ramp_intervals)) + self.assertEqual(plan.pricing_model, 'ramp') + + plan.ramp_intervals = [ + PlanRampInterval( + starting_billing_cycle=1, + unit_amount_in_cents=Money(USD=3000) + ), + PlanRampInterval( + starting_billing_cycle=2, + unit_amount_in_cents=Money(USD=4000) + ), + ] + with self.mock_request('plan/updated_with_ramps.xml'): + plan.save() + def test_preview_subscription_change(self): with self.mock_request('subscription/show.xml'): sub = Subscription.get('123456789012345678901234567890ab')