diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index ee52315937..24d5dbe083 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -63,7 +63,8 @@ final class FundInitiativeViewController $timeline = id(new PhabricatorApplicationTransactionView()) ->setUser($viewer) ->setObjectPHID($initiative->getPHID()) - ->setTransactions($xactions); + ->setTransactions($xactions) + ->setShouldTerminate(true); return $this->buildApplicationPage( array( diff --git a/src/applications/fund/editor/FundBackerEditor.php b/src/applications/fund/editor/FundBackerEditor.php index 067f9ce64c..aabd1d8e60 100644 --- a/src/applications/fund/editor/FundBackerEditor.php +++ b/src/applications/fund/editor/FundBackerEditor.php @@ -13,7 +13,9 @@ final class FundBackerEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); + $types[] = FundBackerTransaction::TYPE_STATUS; + $types[] = FundBackerTransaction::TYPE_REFUND; return $types; } @@ -24,6 +26,8 @@ final class FundBackerEditor switch ($xaction->getTransactionType()) { case FundBackerTransaction::TYPE_STATUS: return $object->getStatus(); + case FundBackerTransaction::TYPE_REFUND: + return null; } return parent::getCustomTransactionOldValue($object, $xaction); @@ -35,6 +39,7 @@ final class FundBackerEditor switch ($xaction->getTransactionType()) { case FundBackerTransaction::TYPE_STATUS: + case FundBackerTransaction::TYPE_REFUND: return $xaction->getNewValue(); } @@ -49,6 +54,8 @@ final class FundBackerEditor case FundBackerTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); return; + case FundBackerTransaction::TYPE_REFUND: + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -60,6 +67,7 @@ final class FundBackerEditor switch ($xaction->getTransactionType()) { case FundBackerTransaction::TYPE_STATUS: + case FundBackerTransaction::TYPE_REFUND: return; } diff --git a/src/applications/fund/editor/FundInitiativeEditor.php b/src/applications/fund/editor/FundInitiativeEditor.php index cbb4074a7e..9fdde7c8b9 100644 --- a/src/applications/fund/editor/FundInitiativeEditor.php +++ b/src/applications/fund/editor/FundInitiativeEditor.php @@ -19,6 +19,7 @@ final class FundInitiativeEditor $types[] = FundInitiativeTransaction::TYPE_RISKS; $types[] = FundInitiativeTransaction::TYPE_STATUS; $types[] = FundInitiativeTransaction::TYPE_BACKER; + $types[] = FundInitiativeTransaction::TYPE_REFUND; $types[] = FundInitiativeTransaction::TYPE_MERCHANT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -39,6 +40,7 @@ final class FundInitiativeEditor case FundInitiativeTransaction::TYPE_STATUS: return $object->getStatus(); case FundInitiativeTransaction::TYPE_BACKER: + case FundInitiativeTransaction::TYPE_REFUND: return null; case FundInitiativeTransaction::TYPE_MERCHANT: return $object->getMerchantPHID(); @@ -57,6 +59,7 @@ final class FundInitiativeEditor case FundInitiativeTransaction::TYPE_RISKS: case FundInitiativeTransaction::TYPE_STATUS: case FundInitiativeTransaction::TYPE_BACKER: + case FundInitiativeTransaction::TYPE_REFUND: case FundInitiativeTransaction::TYPE_MERCHANT: return $xaction->getNewValue(); } @@ -68,7 +71,8 @@ final class FundInitiativeEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { + $type = $xaction->getTransactionType(); + switch ($type) { case FundInitiativeTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; @@ -85,18 +89,18 @@ final class FundInitiativeEditor $object->setStatus($xaction->getNewValue()); return; case FundInitiativeTransaction::TYPE_BACKER: - $backer = id(new FundBackerQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($xaction->getNewValue())) - ->executeOne(); - if (!$backer) { - throw new Exception(pht('No such backer!')); + case FundInitiativeTransaction::TYPE_REFUND: + $amount = $xaction->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + + if ($type == FundInitiativeTransaction::TYPE_REFUND) { + $total = $object->getTotalAsCurrency()->subtract($amount); + } else { + $total = $object->getTotalAsCurrency()->add($amount); } - $backer_amount = $backer->getAmountAsCurrency(); - $total = $object->getTotalAsCurrency()->add($backer_amount); $object->setTotalAsCurrency($total); - return; case PhabricatorTransactions::TYPE_SUBSCRIBERS: case PhabricatorTransactions::TYPE_EDGE: @@ -110,14 +114,45 @@ final class FundInitiativeEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { + $type = $xaction->getTransactionType(); + switch ($type) { case FundInitiativeTransaction::TYPE_NAME: case FundInitiativeTransaction::TYPE_DESCRIPTION: case FundInitiativeTransaction::TYPE_RISKS: case FundInitiativeTransaction::TYPE_STATUS: case FundInitiativeTransaction::TYPE_MERCHANT: + return; case FundInitiativeTransaction::TYPE_BACKER: - // TODO: Maybe we should apply the backer transaction from here? + case FundInitiativeTransaction::TYPE_REFUND: + $backer = id(new FundBackerQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($xaction->getNewValue())) + ->executeOne(); + if (!$backer) { + throw new Exception(pht('Unable to load FundBacker!')); + } + + $subx = array(); + + if ($type == FundInitiativeTransaction::TYPE_BACKER) { + $subx[] = id(new FundBackerTransaction()) + ->setTransactionType(FundBackerTransaction::TYPE_STATUS) + ->setNewValue(FundBacker::STATUS_PURCHASED); + } else { + $amount = $xaction->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $subx[] = id(new FundBackerTransaction()) + ->setTransactionType(FundBackerTransaction::TYPE_STATUS) + ->setNewValue($amount); + } + + $editor = id(new FundBackerEditor()) + ->setActor($this->requireActor()) + ->setContentSource($this->getContentSource()) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($backer, $subx); return; case PhabricatorTransactions::TYPE_SUBSCRIBERS: case PhabricatorTransactions::TYPE_EDGE: diff --git a/src/applications/fund/phortune/FundBackerProduct.php b/src/applications/fund/phortune/FundBackerProduct.php index 3ffd149667..cc13200639 100644 --- a/src/applications/fund/phortune/FundBackerProduct.php +++ b/src/applications/fund/phortune/FundBackerProduct.php @@ -89,7 +89,7 @@ final class FundBackerProduct extends PhortuneProductImplementation { throw new Exception(pht('Unable to load FundBacker!')); } - // Load the actual backing user --they may not be the curent viewer if this + // Load the actual backing user -- they may not be the curent viewer if this // product purchase is completing from a background worker or a merchant // action. @@ -98,20 +98,12 @@ final class FundBackerProduct extends PhortuneProductImplementation { ->withPHIDs(array($backer->getBackerPHID())) ->executeOne(); - $xactions = array(); - $xactions[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) - ->setNewValue(FundBacker::STATUS_PURCHASED); - - $editor = id(new FundBackerEditor()) - ->setActor($actor) - ->setContentSource($this->getContentSource()); - - $editor->applyTransactions($backer, $xactions); - $xactions = array(); $xactions[] = id(new FundInitiativeTransaction()) ->setTransactionType(FundInitiativeTransaction::TYPE_BACKER) + ->setMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT, + $backer->getAmountAsCurrency()->serializeForStorage()) ->setNewValue($backer->getPHID()); $editor = id(new FundInitiativeEditor()) @@ -119,15 +111,38 @@ final class FundBackerProduct extends PhortuneProductImplementation { ->setContentSource($this->getContentSource()); $editor->applyTransactions($this->getInitiative(), $xactions); - - return; } public function didRefundProduct( PhortuneProduct $product, - PhortunePurchase $purchase) { + PhortunePurchase $purchase, + PhortuneCurrency $amount) { $viewer = $this->getViewer(); - // TODO: Undonate. + + $backer = id(new FundBackerQuery()) + ->setViewer($viewer) + ->withPHIDs(array($purchase->getMetadataValue('backerPHID'))) + ->executeOne(); + if (!$backer) { + throw new Exception(pht('Unable to load FundBacker!')); + } + + $xactions = array(); + $xactions[] = id(new FundInitiativeTransaction()) + ->setTransactionType(FundInitiativeTransaction::TYPE_REFUND) + ->setMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT, + $amount->serializeForStorage()) + ->setMetadataValue( + FundInitiativeTransaction::PROPERTY_BACKER, + $backer->getBackerPHID()) + ->setNewValue($backer->getPHID()); + + $editor = id(new FundInitiativeEditor()) + ->setActor($viewer) + ->setContentSource($this->getContentSource()); + + $editor->applyTransactions($this->getInitiative(), $xactions); } } diff --git a/src/applications/fund/storage/FundBackerTransaction.php b/src/applications/fund/storage/FundBackerTransaction.php index 28aed38d29..555c7d2966 100644 --- a/src/applications/fund/storage/FundBackerTransaction.php +++ b/src/applications/fund/storage/FundBackerTransaction.php @@ -4,6 +4,7 @@ final class FundBackerTransaction extends PhabricatorApplicationTransaction { const TYPE_STATUS = 'fund:backer:status'; + const TYPE_REFUND = 'fund:backer:refund'; public function getApplicationName() { return 'fund'; diff --git a/src/applications/fund/storage/FundInitiativeTransaction.php b/src/applications/fund/storage/FundInitiativeTransaction.php index c128d01a23..f473612f30 100644 --- a/src/applications/fund/storage/FundInitiativeTransaction.php +++ b/src/applications/fund/storage/FundInitiativeTransaction.php @@ -8,8 +8,12 @@ final class FundInitiativeTransaction const TYPE_RISKS = 'fund:risks'; const TYPE_STATUS = 'fund:status'; const TYPE_BACKER = 'fund:backer'; + const TYPE_REFUND = 'fund:refund'; const TYPE_MERCHANT = 'fund:merchant'; + const PROPERTY_AMOUNT = 'fund.amount'; + const PROPERTY_BACKER = 'fund.backer'; + public function getApplicationName() { return 'fund'; } @@ -38,6 +42,9 @@ final class FundInitiativeTransaction $phids[] = $new; } break; + case FundInitiativeTransaction::TYPE_REFUND: + $phids[] = $this->getMetadataValue(self::PROPERTY_BACKER); + break; } return $phids; @@ -86,9 +93,23 @@ final class FundInitiativeTransaction } break; case FundInitiativeTransaction::TYPE_BACKER: + $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); return pht( - '%s backed this initiative.', - $this->renderHandleLink($author_phid)); + '%s backed this initiative with %s.', + $this->renderHandleLink($author_phid), + $amount->formatForDisplay()); + case FundInitiativeTransaction::TYPE_REFUND: + $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + + $backer_phid = $this->getMetadataValue(self::PROPERTY_BACKER); + + return pht( + '%s refunded %s to %s.', + $this->renderHandleLink($author_phid), + $amount->formatForDisplay(), + $this->renderHandleLink($backer_phid)); case FundInitiativeTransaction::TYPE_MERCHANT: if ($old === null) { return pht( diff --git a/src/applications/phortune/controller/PhortuneCartCancelController.php b/src/applications/phortune/controller/PhortuneCartCancelController.php index aa726dbe5e..49561ba254 100644 --- a/src/applications/phortune/controller/PhortuneCartCancelController.php +++ b/src/applications/phortune/controller/PhortuneCartCancelController.php @@ -87,7 +87,7 @@ final class PhortuneCartCancelController $request->getStr('refund')); $refund->assertInRange('0.00 USD', $maximum->formatForDisplay()); } catch (Exception $ex) { - $errors[] = $ex; + $errors[] = $ex->getMessage(); $e_refund = pht('Invalid'); } } else { @@ -199,6 +199,7 @@ final class PhortuneCartCancelController return $this->newDialog() ->setTitle($title) + ->setErrors($errors) ->appendChild($body) ->appendChild($form) ->addSubmitButton($button) diff --git a/src/applications/phortune/product/PhortuneProductImplementation.php b/src/applications/phortune/product/PhortuneProductImplementation.php index 14aa4df398..56138befd4 100644 --- a/src/applications/phortune/product/PhortuneProductImplementation.php +++ b/src/applications/phortune/product/PhortuneProductImplementation.php @@ -30,7 +30,8 @@ abstract class PhortuneProductImplementation { public function didRefundProduct( PhortuneProduct $product, - PhortunePurchase $purchase) { + PhortunePurchase $purchase, + PhortuneCurrency $amount) { return; } diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index 1181d301ce..e0b1990711 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -351,8 +351,9 @@ final class PhortuneCart extends PhortuneDAO $this->endReadLocking(); $this->saveTransaction(); + $amount = $refund->getAmountAsCurrency()->negate(); foreach ($this->purchases as $purchase) { - $purchase->getProduct()->didRefundProduct($purchase); + $purchase->getProduct()->didRefundProduct($purchase, $amount); } return $this; diff --git a/src/applications/phortune/storage/PhortuneProduct.php b/src/applications/phortune/storage/PhortuneProduct.php index c984cf86a7..a7d952ed89 100644 --- a/src/applications/phortune/storage/PhortuneProduct.php +++ b/src/applications/phortune/storage/PhortuneProduct.php @@ -78,8 +78,13 @@ final class PhortuneProduct extends PhortuneDAO return $this->getImplementation()->didPurchaseProduct($this, $purchase); } - public function didRefundProduct(PhortunePurchase $purchase) { - return $this->getImplementation()->didRefundProduct($this, $purchase); + public function didRefundProduct( + PhortunePurchase $purchase, + PhortuneCurrency $amount) { + return $this->getImplementation()->didRefundProduct( + $this, + $purchase, + $amount); }