diff --git a/recurly/__init__.py b/recurly/__init__.py index ad525ac7..1e2f48c5 100644 --- a/recurly/__init__.py +++ b/recurly/__init__.py @@ -1210,7 +1210,24 @@ def refund_amount(self, amount_in_cents, refund_options = {}): if 'refund_method' not in refund_options: refund_options = { 'refund_method': 'credit_first' } - amount_element = self._refund_open_amount_xml(amount_in_cents, + amount_element = self._refund_open_amount_xml('amount_in_cents', + amount_in_cents, + refund_options) + return self._create_refund_invoice(amount_element) + + def refund_percentage(self, percentage, refund_options = {}): + # For backwards compatibility + # TODO the consequent branch of this conditional should eventually be removed + # and we should document that as a breaking change in the changelog. + # The same change should be applied to the refund() method + if (isinstance(refund_options, six.string_types)): + refund_options = { 'refund_method': refund_options } + else: + if 'refund_method' not in refund_options: + refund_options = { 'refund_method': 'credit_first' } + + amount_element = self._refund_open_amount_xml('percentage', + percentage, refund_options) return self._create_refund_invoice(amount_element) @@ -1229,10 +1246,10 @@ def refund(self, adjustments, refund_options = {}): refund_options) return self._create_refund_invoice(adjustments_element) - def _refund_open_amount_xml(self, amount_in_cents, refund_options): + def _refund_open_amount_xml(self, refund_type, refund_amount, refund_options): elem = ElementTreeBuilder.Element(self.nodename) - elem.append(Resource.element_for_value('amount_in_cents', - amount_in_cents)) + elem.append(Resource.element_for_value(refund_type, + refund_amount)) # Need to sort the keys for tests to pass in python 2 and 3 # Can remove `sorted` when we drop python 2 support @@ -1247,15 +1264,22 @@ def _refund_line_items_xml(self, line_items, refund_options): for item in line_items: adj_elem = ElementTreeBuilder.Element('adjustment') - adj_elem.append(Resource.element_for_value('uuid', - item['adjustment'].uuid)) - adj_elem.append(Resource.element_for_value('quantity', - item['quantity'])) + adj_elem.append(Resource.element_for_value('uuid', item['adjustment'].uuid)) + + if 'quantity' in item: + adj_elem.append(Resource.element_for_value('quantity', item['quantity'])) if 'quantity_decimal' in item: adj_elem.append(Resource.element_for_value('quantity_decimal', item['quantity_decimal'])) - adj_elem.append(Resource.element_for_value('prorate', item['prorate'])) + if 'percentage' in item: + adj_elem.append(Resource.element_for_value('percentage', item['percentage'])) + + if 'amount_in_cents' in item: + adj_elem.append(Resource.element_for_value('amount_in_cents', item['amount_in_cents'])) + + if 'prorate' in item: + adj_elem.append(Resource.element_for_value('prorate', item['prorate'])) line_items_elem.append(adj_elem) elem.append(line_items_elem) diff --git a/tests/fixtures/invoice/line-item-refunded-amount-in-cents.xml b/tests/fixtures/invoice/line-item-refunded-amount-in-cents.xml new file mode 100644 index 00000000..cac366c0 --- /dev/null +++ b/tests/fixtures/invoice/line-item-refunded-amount-in-cents.xml @@ -0,0 +1,39 @@ +POST https://api.recurly.com/v2/invoices/4ba1531325014b4f969cd13676f514d8/refund 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 + + + + + + 2bc3cf4cb513049c6aec1b419c97b508 + 500 + false + + + Credit Customer Notes + Description + credit_first + + +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/invoices/4ba1531325014b4f969cd13676f514d8 + + + + 4ba1531325014b4f969cd13676f514d8 + invoicemock + 1001 + + -500 + 0 + -500 + 2009-11-03T23:27:46-08:00 + + test charge + 2009-11-03T23:27:46-08:00 + diff --git a/tests/fixtures/invoice/line-item-refunded-percentage.xml b/tests/fixtures/invoice/line-item-refunded-percentage.xml new file mode 100644 index 00000000..9ed3ad66 --- /dev/null +++ b/tests/fixtures/invoice/line-item-refunded-percentage.xml @@ -0,0 +1,39 @@ +POST https://api.recurly.com/v2/invoices/4ba1531325014b4f969cd13676f514d8/refund 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 + + + + + + 2bc3cf4cb513049c6aec1b419c97b508 + 50 + false + + + Credit Customer Notes + Description + credit_first + + +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/invoices/4ba1531325014b4f969cd13676f514d8 + + + + 4ba1531325014b4f969cd13676f514d8 + invoicemock + 1001 + + -500 + 0 + -500 + 2009-11-03T23:27:46-08:00 + + test charge + 2009-11-03T23:27:46-08:00 + diff --git a/tests/fixtures/invoice/refunded-percentage.xml b/tests/fixtures/invoice/refunded-percentage.xml new file mode 100644 index 00000000..b97d0bac --- /dev/null +++ b/tests/fixtures/invoice/refunded-percentage.xml @@ -0,0 +1,33 @@ +POST https://api.recurly.com/v2/invoices/4ba1531325014b4f969cd13676f514d8/refund 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 + + + + 50 + Credit Customer Notes + Description + credit_first + + +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/invoices/4ba1531325014b4f969cd13676f514d8 + + + + 4ba1531325014b4f969cd13676f514d8 + invoicemock + 1001 + + -50 + 0 + -50 + 2009-11-03T23:27:46-08:00 + + test charge + 2009-11-03T23:27:46-08:00 + diff --git a/tests/test_resources.py b/tests/test_resources.py index cc33cb02..42620cba 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -1678,6 +1678,23 @@ def test_invoice_refund_amount(self): refund_invoice = invoice.refund_amount(1000, options) self.assertEqual(refund_invoice.subtotal_in_cents, -1000) + def test_invoice_refund_percentage(self): + account = Account(account_code='invoice%s' % self.test_id) + with self.mock_request('invoice/account-created.xml'): + account.save() + + with self.mock_request('invoice/invoiced.xml'): + invoice = account.invoice().charge_invoice + + with self.mock_request('invoice/refunded-percentage.xml'): + options = { + 'refund_method': 'credit_first', + 'credit_customer_notes': 'Credit Customer Notes', + 'description': 'Description' + } + refund_invoice = invoice.refund_percentage(50, options) + self.assertEqual(refund_invoice.subtotal_in_cents, -50) + def test_invoice_refund(self): account = Account(account_code='invoice%s' % self.test_id) with self.mock_request('invoice/account-created.xml'): @@ -1697,6 +1714,42 @@ def test_invoice_refund(self): refund_invoice = invoice.refund(line_items, options) self.assertEqual(refund_invoice.subtotal_in_cents, -1000) + def test_invoice_refund_line_item_percentage(self): + account = Account(account_code='invoice%s' % self.test_id) + with self.mock_request('invoice/account-created.xml'): + account.save() + + with self.mock_request('invoice/invoiced-line-items.xml'): + invoice = account.invoice().charge_invoice + + with self.mock_request('invoice/line-item-refunded-percentage.xml'): + line_items = [{ 'adjustment': invoice.line_items[0], 'percentage': 50, 'prorate': False }] + options = { + 'refund_method': 'credit_first', + 'credit_customer_notes': 'Credit Customer Notes', + 'description': 'Description' + } + refund_invoice = invoice.refund(line_items, options) + self.assertEqual(refund_invoice.subtotal_in_cents, -500) + + def test_invoice_refund_line_item_amount_in_cents(self): + account = Account(account_code='invoice%s' % self.test_id) + with self.mock_request('invoice/account-created.xml'): + account.save() + + with self.mock_request('invoice/invoiced-line-items.xml'): + invoice = account.invoice().charge_invoice + + with self.mock_request('invoice/line-item-refunded-amount-in-cents.xml'): + line_items = [{ 'adjustment': invoice.line_items[0], 'amount_in_cents': 500, 'prorate': False }] + options = { + 'refund_method': 'credit_first', + 'credit_customer_notes': 'Credit Customer Notes', + 'description': 'Description' + } + refund_invoice = invoice.refund(line_items, options) + self.assertEqual(refund_invoice.subtotal_in_cents, -500) + def test_invoice_collect(self): with self.mock_request('invoice/show-invoice.xml'): invoice = Invoice.get("6019")