Skip to content

Commit

Permalink
Support open amount and line item refunds
Browse files Browse the repository at this point in the history
  • Loading branch information
bhelx committed Feb 6, 2015
1 parent a1f4321 commit d5946fc
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Recurly PHP Client Library CHANGELOG

## Unreleased

* Added open amount and line item refund support to Invoices [133](https://github.com/recurly/recurly-client-php/pull/133)

This comment has been minimized.

Copy link
@drewish

drewish Feb 6, 2015

Let's get this matching the style of the other change log entries.


## Version 2.4.0 (Feb 2nd, 2014)

* Force cURL to validate SSL certificates [#122](https://github.com/recurly/recurly-client-php/pull/122)
Expand Down
22 changes: 22 additions & 0 deletions Tests/Recurly/Adjustment_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
22 changes: 22 additions & 0 deletions Tests/Recurly/Invoice_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

This comment has been minimized.

Copy link
@drewish

drewish Feb 6, 2015

I wish there was a better way to examine the XML we're generating here. We really have no idea what it's outputting.


$refund_invoice = $invoice->refund($adjustments);
$this->assertEquals($refund_invoice->subtotal_in_cents, -1000);
}
}
21 changes: 21 additions & 0 deletions Tests/fixtures/invoices/refund-201.xml
Original file line number Diff line number Diff line change
@@ -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

<invoice href="https://api.recurly.com/v2/invoices/1001">
<account href="https://api.recurly.com/v2/accounts/1"/>
<subscription href="https://api.recurly.com/v2/subscriptions/1234567890abcdef"/>
<uuid>012345678901234567890123456789aa</uuid>
<state>collected</state>
<invoice_number type="integer">abcdef1234567890</invoice_number>
<po_number></po_number>
<vat_number></vat_number>
<subtotal_in_cents type="integer">-1000</subtotal_in_cents>
<tax_in_cents type="integer">0</tax_in_cents>
<total_in_cents type="integer">2995</total_in_cents>
<currency>USD</currency>
<created_at type="datetime">2012-05-28T17:44:13Z</created_at>
<closed_at type="datetime">2012-05-28T17:44:13Z</closed_at>
<net_terms type="integer">0</net_terms>
<collection_method>automatic</collection_method>
</invoice>
31 changes: 31 additions & 0 deletions lib/recurly/adjustment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
41 changes: 41 additions & 0 deletions lib/recurly/invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
10 changes: 9 additions & 1 deletion lib/recurly/resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<foo></foo>` over `<foo/>`.
return $this->renderXML($doc);
}

public function createDocument() {
return new DOMDocument("1.0");
}

public function renderXML($doc) {
return $doc->saveXML(null, LIBXML_NOEMPTYTAG);
}

Expand Down

0 comments on commit d5946fc

Please sign in to comment.