From f5b7790ede50c32237c6068cf3f8f9655c6dc00e Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Tue, 13 Jan 2015 13:07:17 -0800 Subject: [PATCH] Support open amount and line item refunds --- CHANGELOG.md | 5 ++++ Tests/Recurly/Adjustment_Test.php | 22 ++++++++++++++ Tests/Recurly/Invoice_Test.php | 22 ++++++++++++++ Tests/fixtures/invoices/refund-201.xml | 21 +++++++++++++ lib/recurly/adjustment.php | 31 +++++++++++++++++++ lib/recurly/invoice.php | 41 ++++++++++++++++++++++++++ lib/recurly/resource.php | 10 ++++++- 7 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 Tests/fixtures/invoices/refund-201.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c99e924..886bc5f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Recurly PHP Client Library CHANGELOG +## Unreleased + +* Added adjustment refund support: `refund()` and `toRefundAttributes()` on `Recurly_Adjustment` [133](https://github.com/recurly/recurly-client-php/pull/133) +* Added invoice refund supprt: `refund()` and `refundAmount()` on `Recurly_Invoice` [133](https://github.com/recurly/recurly-client-php/pull/133) + ## Version 2.4.0 (Feb 2nd, 2014) * Force cURL to validate SSL certificates [#122](https://github.com/recurly/recurly-client-php/pull/122) diff --git a/Tests/Recurly/Adjustment_Test.php b/Tests/Recurly/Adjustment_Test.php index 336a1063..540dfcd0 100644 --- a/Tests/Recurly/Adjustment_Test.php +++ b/Tests/Recurly/Adjustment_Test.php @@ -60,6 +60,28 @@ public function testDelete() { $adjustment->delete(); } + public function testToRefundAttributes() { + $this->client->addResponse('GET', '/adjustments/abcdef1234567890', 'adjustments/show-200.xml'); + + $adjustment = Recurly_Adjustment::get('abcdef1234567890', $this->client); + + $attributes = $adjustment->toRefundAttributes(); + $this->assertEquals($attributes['uuid'], $adjustment->uuid); + $this->assertEquals($attributes['prorate'], false); + $this->assertEquals($attributes['quantity'], $adjustment->quantity); + } + + public function testAdjustmentRefund() { + $this->client->addResponse('GET', '/adjustments/abcdef1234567890', 'adjustments/show-200.xml'); + $this->client->addResponse('GET', 'https://api.recurly.com/v2/invoices/1234', 'invoices/show-200.xml'); + $this->client->addResponse('POST', 'https://api.recurly.com/v2/invoices/1001/refund', 'invoices/refund-201.xml'); + + $adjustment = Recurly_Adjustment::get('abcdef1234567890', $this->client); + + $refund_invoice = $adjustment->refund(); + $this->assertEquals($refund_invoice->subtotal_in_cents, -1000); + } + public function testXml() { $charge = new Recurly_Adjustment(); $charge->account_code = '1'; diff --git a/Tests/Recurly/Invoice_Test.php b/Tests/Recurly/Invoice_Test.php index 9c8c6c11..80960ecd 100644 --- a/Tests/Recurly/Invoice_Test.php +++ b/Tests/Recurly/Invoice_Test.php @@ -121,4 +121,26 @@ public function testGetInvoicePdf() { $result = Recurly_Invoice::getInvoicePdf('1001', 'en-GB', $this->client); $this->assertEquals(array('/invoices/1001', 'en-GB'), $result); } + + public function testRefundAmount() { + $this->client->addResponse('POST', 'https://api.recurly.com/v2/invoices/1001/refund', 'invoices/refund-201.xml'); + $invoice = Recurly_Invoice::get('1001', $this->client); + + $refund_invoice = $invoice->refundAmount(1000); + $this->assertEquals($refund_invoice->subtotal_in_cents, -1000); + } + + public function testRefund() { + $this->client->addResponse('POST', 'https://api.recurly.com/v2/invoices/1001/refund', 'invoices/refund-201.xml'); + $invoice = Recurly_Invoice::get('1001', $this->client); + $line_items = $invoice->line_items; + + $adjustment_map = function($line_item) { + return $line_item->toRefundAttributes(); + }; + $adjustments = array_map($adjustment_map, $line_items); + + $refund_invoice = $invoice->refund($adjustments); + $this->assertEquals($refund_invoice->subtotal_in_cents, -1000); + } } diff --git a/Tests/fixtures/invoices/refund-201.xml b/Tests/fixtures/invoices/refund-201.xml new file mode 100644 index 00000000..aeaef55f --- /dev/null +++ b/Tests/fixtures/invoices/refund-201.xml @@ -0,0 +1,21 @@ +HTTP/1.1 201 Created +Content-Type: application/xml; charset=utf-8 +Location: https://api.recurly.com/v2/invoices/created-invoice + + + + + 012345678901234567890123456789aa + collected + abcdef1234567890 + + + -1000 + 0 + 2995 + USD + 2012-05-28T17:44:13Z + 2012-05-28T17:44:13Z + 0 + automatic + diff --git a/lib/recurly/adjustment.php b/lib/recurly/adjustment.php index 0b96225e..5c790262 100644 --- a/lib/recurly/adjustment.php +++ b/lib/recurly/adjustment.php @@ -27,6 +27,37 @@ public function delete() { return Recurly_Base::_delete($this->getHref(), $this->_client); } + /** + * Allows you to refund this particular item if it's a part of + * an invoice. It does this by calling the invoice's refund() + * Only 'invoiced' adjustments can be refunded. + * + * @param Integer the quantity you wish to refund, defaults to refunding the entire quantity + * @param Boolean indicates whether you want this adjustment refund prorated + * @return Recurly_Invoice the new refund invoice + * @throws Recurly_Error if the adjustment cannot be refunded. + */ + public function refund($quantity = null, $prorate = false) { + if ($this->state == 'pending') { + throw new Recurly_Error("Only invoiced adjustments can be refunded"); + } + $invoice = $this->invoice->get(); + return $invoice->refund($this->toRefundAttributes($quantity, $prorate)); + } + + /** + * Converts this adjustment into the attributes needed for a refund. + * + * @param Integer the quantity you wish to refund, defaults to refunding the entire quantity + * @param Boolean indicates whether you want this adjustment refund prorated + * @return Array an array of refund attributes to be fed into invoice->refund() + */ + public function toRefundAttributes($quantity = null, $prorate = false) { + if (is_null($quantity)) $quantity = $this->quantity; + + return array('uuid' => $this->uuid, 'quantity' => $quantity, 'prorate' => $prorate); + } + protected function createUriForAccount() { if (empty($this->account_code)) throw new Recurly_Error("'account_code' is not specified"); diff --git a/lib/recurly/invoice.php b/lib/recurly/invoice.php index f4df4c88..4fa425e1 100644 --- a/lib/recurly/invoice.php +++ b/lib/recurly/invoice.php @@ -74,6 +74,47 @@ public function invoiceNumberWithPrefix() { return $this->invoice_number_prefix . $this->invoice_number; } + /** + * Refunds an open amount from the invoice and returns a new refund invoice + * @param Integer amount in cents to refund from this invoice + * @return Recurly_Invoice a new refund invoice + */ + public function refundAmount($amount_in_cents) { + $doc = $this->createDocument(); + + $root = $doc->appendChild($doc->createElement($this->getNodeName())); + $root->appendChild($doc->createElement('amount_in_cents', $amount_in_cents)); + + return $this->createRefundInvoice($this->renderXML($doc)); + } + + /** + * Refunds given line items from an invoice and returns new refund invoice + * @param Array refund attributes or Array of refund attributes to refund (see 'REFUND ATTRIBUTES' in docs or Recurly_Adjustment#toRefundAttributes) + * @return Recurly_Invoice a new refund invoice + */ + public function refund($line_items) { + if (isset($line_items['uuid'])) { $line_items = array($line_items); } + + $doc = $this->createDocument(); + + $root = $doc->appendChild($doc->createElement($this->getNodeName())); + $line_items_node = $root->appendChild($doc->createElement('line_items')); + + foreach ($line_items as $line_item) { + $adjustment_node = $line_items_node->appendChild($doc->createElement('adjustment')); + $adjustment_node->appendChild($doc->createElement('uuid', $line_item['uuid'])); + $adjustment_node->appendChild($doc->createElement('quantity', $line_item['quantity'])); + $adjustment_node->appendChild($doc->createElement('prorate', $line_item['prorate'] ? 'true' : 'false')); + } + + return $this->createRefundInvoice($this->renderXML($doc)); + } + + protected function createRefundInvoice($xml_string) { + return self::_post($this->uri() . '/refund', $xml_string, $this->_client); + } + protected function getNodeName() { return 'invoice'; } diff --git a/lib/recurly/resource.php b/lib/recurly/resource.php index 017925ba..e7fa5ba5 100644 --- a/lib/recurly/resource.php +++ b/lib/recurly/resource.php @@ -91,11 +91,19 @@ protected function _save($method, $uri) public function xml() { - $doc = new DOMDocument("1.0"); + $doc = $this->createDocument(); $root = $doc->appendChild($doc->createElement($this->getNodeName())); $this->populateXmlDoc($doc, $root, $this); // To be able to consistently run tests across different XML libraries, // favor `` over ``. + return $this->renderXML($doc); + } + + public function createDocument() { + return new DOMDocument("1.0"); + } + + public function renderXML($doc) { return $doc->saveXML(null, LIBXML_NOEMPTYTAG); }