Skip to content

Commit

Permalink
Add payment implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ludofleury committed Aug 16, 2013
1 parent c095d6f commit 91d4954
Show file tree
Hide file tree
Showing 2 changed files with 345 additions and 0 deletions.
180 changes: 180 additions & 0 deletions src/Playbloom/Payment/Operation/Payment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

namespace Playbloom\Payment\Operation;

use Playbloom\Payment\Operation\PaymentInterface;
use Playbloom\Payment\Operation\OperationId;
use Playbloom\Payment\CardInterface;
use Playbloom\Payment\Exception\OperationException;
use Money\Money;

/**
* Card payment operation
*
* @author Ludovic Fleury <ludo.fleury@gmail.com>
*/
class Payment implements PaymentInterface
{
/**
* OperationId
*
* @var Playbloom\Payment\OperationId
*/
private $id;

/**
* @var string
*/
private $label;

/**
* @var Playbloom\Payment\CardInterface
*/
private $card;

/**
* @var Money\Money
*/
private $amount;

/**
* @var Playbloom\Payment\Operation\RefundInterface[]
*/
public $refunds;

/**
* Constructor
*
* @param string $id
* @param string $label
* @param CardInterface $card
* @param Money $amount
*/
public function __construct(OperationId $id, $label, Money $amount, CardInterface $card)
{
$this->id = $id;
$this->label = $label;
$this->amount = $amount;
$this->card = $card;
$this->refunds = [];
}

/**
* Get the string representation
*
* @return string
*/
public function __toString()
{
return sprintf(
'Payment "%s" for "%s": %s %s',
$this->id,
$this->label,
$this->getAmount()->getAmount() / 100,
$this->getAmount()->getCurrency()
);
}

/**
* Get the unique transaction identifier
*
* @return Playbloom\Payment\OperationId
*/
public function getId()
{
return $this->id;
}

/**
* Get the related Card
*
* @return Playbloom\Payment\CardInterface
*/
public function getCard()
{
return $this->card;
}

/**
* Get the label
*
* @return string
*/
public function getLabel()
{
return $this->label;
}

/**
* Get the amount
*
* @return Money\Money
*/
public function getAmount()
{
return $this->amount;
}

/**
* Check whether or not the Payment has been refunded
*
* @return boolean
*/
public function isRefunded()
{
return $this->getRefundedAmount()->isPositive();
}

/**
* Get the refundable money
*
* @return Money\Money
*/
public function getRefundableAmount()
{
$amount = $this->getAmount()->subtract($this->getRefundedAmount());

return $amount->isNegative() ? new Money(0, $this->amount->getCurrency()) : $amount;
}

/**
* Get the refunded money
*
* @return Money\Money
*/
public function getRefundedAmount()
{
$refunded = new Money(0, $this->amount->getCurrency());

foreach ($this->refunds as $refund) {
$refunded = $refunded->add($refund->getAmount());
}

return $refunded;
}

/**
* Refund
*
* @param RefundInterface $refund
*
* @throws Playbloom\Payment\Exception\OperationException
*
* @return static
*/
public function refund(RefundInterface $refund)
{
if ($refund->getPayment() !== $this) {
throw new OperationException(sprintf('Unable to refund [%s] with [%s]: operations are not related', $this, $refund));
}

$hash = spl_object_hash($refund);
if (isset($this->refunds[$hash])) {
throw new OperationException(sprintf('Unable to refund [%s] with [%s]: operations are already related', $this, $refund));
}

$this->refunds[$hash] = $refund;

return $this;
}
}
165 changes: 165 additions & 0 deletions tests/Playbloom/Payment/Tests/Units/Operation/Payment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

namespace Playbloom\Payment\Tests\Units\Operation;

use Playbloom\Payment\Tests\Units\Operation\AbstractOperation as OperationTest;
use Playbloom\Payment\Operation;
use Playbloom\Payment\Operation\OperationId as Id;
use Money\Money;
use Mock;

class Payment extends OperationTest
{
/**
* Data provider required by OperationTest
*/
public function provideTestedInstance()
{
$toString = 'Payment "id" for "saving the world": 1337.42 EUR';
$id = new Id('id');
$label = 'saving the world';
$amount = Money::EUR(133742);
$card = new Mock\Playbloom\Payment\CardInterface();

return [
[
new Operation\Payment($id, $label, $amount, $card),
$toString,
$id,
$label,
$amount,
$card
]
];
}

public function testIsNotRefunded()
{
$payment = $this->createInstance();

$this
->boolean($payment->isRefunded())
->isFalse()
;
}

public function testRefundUnrelatedOperation()
{
$payment = $this->createInstance(['label' => 'original']);
$anotherPayment = $this->createInstance(['label' => 'another']);

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $anotherPayment;
$refund->getMockController()->getAmount = Money::EUR(1);
$refund->getMockController()->__toString = 'Refund for another payment';

$this
->exception(function () use ($payment, $refund) {
$payment->refund($refund);
})
->isInstanceOf('Playbloom\\Payment\\Exception\\OperationException')
->hasMessage(sprintf('Unable to refund [%s] with [Refund for another payment]: operations are not related', $payment->__toString()))
;
}

public function testRefundAlreadyRelatedOperation()
{
$payment = $this->createInstance(['label' => 'original']);

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $payment;
$refund->getMockController()->getAmount = Money::EUR(1);
$refund->getMockController()->__toString = 'Refund for original payment';
$payment->refund($refund);

$this
->exception(function () use ($payment, $refund) {
$payment->refund($refund);
})
->isInstanceOf('Playbloom\\Payment\\Exception\\OperationException')
->hasMessage(sprintf('Unable to refund [%s] with [Refund for original payment]: operations are already related', $payment->__toString()))
;
}

public function testIsRefunded()
{
$payment = $this->createInstance(['amount' => Money::EUR(10000)]);

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $payment;
$refund->getMockController()->getAmount = Money::EUR(1);

$payment->refund($refund);

$this
->boolean($payment->isRefunded())
->isTrue()
;
}

public function testGetRefundableAmount()
{
$payment = $this->createInstance(['amount' => Money::EUR(10000)]);

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $payment;
$refund->getMockController()->getAmount = Money::EUR(1000);
$payment->refund($refund);
$this
->object($payment->getRefundableAmount())
->isInstanceOf('Money\\Money')
->boolean($payment->getRefundableAmount()->equals(Money::EUR(9000)))
->isTrue()
;

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $payment;
$refund->getMockController()->getAmount = Money::EUR(9000);
$payment->refund($refund);
$this
->object($payment->getRefundableAmount())
->isInstanceOf('Money\\Money')
->boolean($payment->getRefundableAmount()->equals(Money::EUR(0)))
->isTrue()
;

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $payment;
$refund->getMockController()->getAmount = Money::EUR(500);
$payment->refund($refund);
$this
->object($payment->getRefundableAmount())
->isInstanceOf('Money\\Money')
->boolean($payment->getRefundableAmount()->equals(Money::EUR(0)))
->isTrue()
;
}

public function testGetRefundedAmount()
{
$payment = $this->createInstance(['amount' => Money::EUR(10000)]);

$refund = new Mock\Playbloom\Payment\Operation\RefundInterface;
$refund->getMockController()->getPayment = $payment;
$refund->getMockController()->getAmount = Money::EUR(2000);

$payment->refund($refund);

$this
->object($payment->getRefundableAmount())
->isInstanceOf('Money\\Money')
->boolean($payment->getRefundedAmount()->equals(Money::EUR(2000)))
->isTrue()
;
}

private function createInstance(array $arguments = [])
{
$id = isset($arguments['id']) ? $arguments['id'] : new Id('id');
$label = isset($arguments['label']) ? $arguments['label'] : 'saving the world';
$amount = isset($arguments['amount']) ? $arguments['amount'] : Money::EUR(133742);
$card = isset($arguments['card']) ? $arguments['card'] : new Mock\Playbloom\Payment\CardInterface();

return new Operation\Payment($id, $label, $amount, $card);
}
}

0 comments on commit 91d4954

Please sign in to comment.