Page MenuHomePhorge

No OneTemporary

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
@@ -1,187 +1,188 @@
<?php
final class FundInitiativeViewController
extends FundController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$initiative = id(new FundInitiativeQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$initiative) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($initiative->getMonogram());
$title = pht(
'%s %s',
$initiative->getMonogram(),
$initiative->getName());
if ($initiative->isClosed()) {
$status_icon = 'fa-times';
$status_color = 'bluegrey';
} else {
$status_icon = 'fa-check';
$status_color = 'bluegrey';
}
$status_name = idx(
FundInitiative::getStatusNameMap(),
$initiative->getStatus());
$header = id(new PHUIHeaderView())
->setObjectName($initiative->getMonogram())
->setHeader($initiative->getName())
->setUser($viewer)
->setPolicyObject($initiative)
->setStatus($status_icon, $status_color, $status_name);
$properties = $this->buildPropertyListView($initiative);
$actions = $this->buildActionListView($initiative);
$properties->setActionList($actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($properties);
$xactions = id(new FundInitiativeTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($initiative->getPHID()))
->execute();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($initiative->getPHID())
- ->setTransactions($xactions);
+ ->setTransactions($xactions)
+ ->setShouldTerminate(true);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
$timeline,
),
array(
'title' => $title,
));
}
private function buildPropertyListView(FundInitiative $initiative) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($initiative);
$owner_phid = $initiative->getOwnerPHID();
$merchant_phid = $initiative->getMerchantPHID();
$this->loadHandles(
array(
$owner_phid,
$merchant_phid,
));
$view->addProperty(
pht('Owner'),
$this->getHandle($owner_phid)->renderLink());
$view->addProperty(
pht('Payable to Merchant'),
$this->getHandle($merchant_phid)->renderLink());
$view->addProperty(
pht('Total Funding'),
$initiative->getTotalAsCurrency()->formatForDisplay());
$view->invokeWillRenderEvent();
$description = $initiative->getDescription();
if (strlen($description)) {
$description = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($description),
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addTextContent($description);
}
$risks = $initiative->getRisks();
if (strlen($risks)) {
$risks = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($risks),
'default',
$viewer);
$view->addSectionHeader(pht('Risks/Challenges'));
$view->addTextContent($risks);
}
return $view;
}
private function buildActionListView(FundInitiative $initiative) {
$viewer = $this->getRequest()->getUser();
$id = $initiative->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$initiative,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($initiative);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Initiative'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($this->getApplicationURI("/edit/{$id}/")));
if ($initiative->isClosed()) {
$close_name = pht('Reopen Initiative');
$close_icon = 'fa-check';
} else {
$close_name = pht('Close Initiative');
$close_icon = 'fa-times';
}
$view->addAction(
id(new PhabricatorActionView())
->setName($close_name)
->setIcon($close_icon)
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($this->getApplicationURI("/close/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Back Initiative'))
->setIcon('fa-money')
->setDisabled($initiative->isClosed())
->setWorkflow(true)
->setHref($this->getApplicationURI("/back/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View Backers'))
->setIcon('fa-bank')
->setHref($this->getApplicationURI("/backers/{$id}/")));
return $view;
}
}
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
@@ -1,69 +1,77 @@
<?php
final class FundBackerEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorFundApplication';
}
public function getEditorObjectsDescription() {
return pht('Fund Backing');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
+
$types[] = FundBackerTransaction::TYPE_STATUS;
+ $types[] = FundBackerTransaction::TYPE_REFUND;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundBackerTransaction::TYPE_STATUS:
return $object->getStatus();
+ case FundBackerTransaction::TYPE_REFUND:
+ return null;
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundBackerTransaction::TYPE_STATUS:
+ case FundBackerTransaction::TYPE_REFUND:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundBackerTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue());
return;
+ case FundBackerTransaction::TYPE_REFUND:
+ return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundBackerTransaction::TYPE_STATUS:
+ case FundBackerTransaction::TYPE_REFUND:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
}
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
@@ -1,200 +1,235 @@
<?php
final class FundInitiativeEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorFundApplication';
}
public function getEditorObjectsDescription() {
return pht('Fund Initiatives');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = FundInitiativeTransaction::TYPE_NAME;
$types[] = FundInitiativeTransaction::TYPE_DESCRIPTION;
$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;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundInitiativeTransaction::TYPE_NAME:
return $object->getName();
case FundInitiativeTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
case FundInitiativeTransaction::TYPE_RISKS:
return $object->getRisks();
case FundInitiativeTransaction::TYPE_STATUS:
return $object->getStatus();
case FundInitiativeTransaction::TYPE_BACKER:
+ case FundInitiativeTransaction::TYPE_REFUND:
return null;
case FundInitiativeTransaction::TYPE_MERCHANT:
return $object->getMerchantPHID();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundInitiativeTransaction::TYPE_NAME:
case FundInitiativeTransaction::TYPE_DESCRIPTION:
case FundInitiativeTransaction::TYPE_RISKS:
case FundInitiativeTransaction::TYPE_STATUS:
case FundInitiativeTransaction::TYPE_BACKER:
+ case FundInitiativeTransaction::TYPE_REFUND:
case FundInitiativeTransaction::TYPE_MERCHANT:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
- switch ($xaction->getTransactionType()) {
+ $type = $xaction->getTransactionType();
+ switch ($type) {
case FundInitiativeTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_RISKS:
$object->setRisks($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_MERCHANT:
$object->setMerchantPHID($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_STATUS:
$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:
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
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:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case FundInitiativeTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Initiative name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
case FundInitiativeTransaction::TYPE_MERCHANT:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Payable merchant is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
} else if ($xactions) {
$merchant_phid = last($xactions)->getNewValue();
// Make sure the actor has permission to edit the merchant they're
// selecting. You aren't allowed to send payments to an account you
// do not control.
$merchants = id(new PhortuneMerchantQuery())
->setViewer($this->requireActor())
->withPHIDs(array($merchant_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
if (!$merchants) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'You must specify a merchant account you control as the '.
'recipient of funds from this initiative.'),
last($xactions));
$errors[] = $error;
}
}
break;
}
return $errors;
}
}
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
@@ -1,133 +1,148 @@
<?php
final class FundBackerProduct extends PhortuneProductImplementation {
private $initiativePHID;
private $initiative;
private $viewer;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function getRef() {
return $this->getInitiativePHID();
}
public function getName(PhortuneProduct $product) {
$initiative = $this->getInitiative();
return pht(
'Fund %s %s',
$initiative->getMonogram(),
$initiative->getName());
}
public function getPriceAsCurrency(PhortuneProduct $product) {
return PhortuneCurrency::newEmptyCurrency();
}
public function setInitiativePHID($initiative_phid) {
$this->initiativePHID = $initiative_phid;
return $this;
}
public function getInitiativePHID() {
return $this->initiativePHID;
}
public function setInitiative(FundInitiative $initiative) {
$this->initiative = $initiative;
return $this;
}
public function getInitiative() {
return $this->initiative;
}
public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs) {
$initiatives = id(new FundInitiativeQuery())
->setViewer($viewer)
->withPHIDs($refs)
->execute();
$initiatives = mpull($initiatives, null, 'getPHID');
$objects = array();
foreach ($refs as $ref) {
$object = id(new FundBackerProduct())
->setViewer($viewer)
->setInitiativePHID($ref);
$initiative = idx($initiatives, $ref);
if ($initiative) {
$object->setInitiative($initiative);
}
$objects[] = $object;
}
return $objects;
}
public function didPurchaseProduct(
PhortuneProduct $product,
PhortunePurchase $purchase) {
$viewer = $this->getViewer();
$backer = id(new FundBackerQuery())
->setViewer($viewer)
->withPHIDs(array($purchase->getMetadataValue('backerPHID')))
->executeOne();
if (!$backer) {
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.
$actor = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->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())
->setActor($actor)
->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
@@ -1,20 +1,21 @@
<?php
final class FundBackerTransaction
extends PhabricatorApplicationTransaction {
const TYPE_STATUS = 'fund:backer:status';
+ const TYPE_REFUND = 'fund:backer:refund';
public function getApplicationName() {
return 'fund';
}
public function getApplicationTransactionType() {
return FundBackerPHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
}
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
@@ -1,189 +1,210 @@
<?php
final class FundInitiativeTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'fund:name';
const TYPE_DESCRIPTION = 'fund:description';
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';
}
public function getApplicationTransactionType() {
return FundInitiativePHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case FundInitiativeTransaction::TYPE_MERCHANT:
if ($old) {
$phids[] = $old;
}
if ($new) {
$phids[] = $new;
}
break;
+ case FundInitiativeTransaction::TYPE_REFUND:
+ $phids[] = $this->getMetadataValue(self::PROPERTY_BACKER);
+ break;
}
return $phids;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case FundInitiativeTransaction::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this initiative.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this initiative from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case FundInitiativeTransaction::TYPE_RISKS:
return pht(
'%s edited the risks for this initiative.',
$this->renderHandleLink($author_phid));
case FundInitiativeTransaction::TYPE_DESCRIPTION:
return pht(
'%s edited the description of this initiative.',
$this->renderHandleLink($author_phid));
case FundInitiativeTransaction::TYPE_STATUS:
switch ($new) {
case FundInitiative::STATUS_OPEN:
return pht(
'%s reopened this initiative.',
$this->renderHandleLink($author_phid));
case FundInitiative::STATUS_CLOSED:
return pht(
'%s closed this initiative.',
$this->renderHandleLink($author_phid));
}
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(
'%s set this initiative to pay to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s changed the merchant receiving funds from this '.
'initiative from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
}
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case FundInitiativeTransaction::TYPE_NAME:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s renamed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
case FundInitiativeTransaction::TYPE_DESCRIPTION:
return pht(
'%s updated the description for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case FundInitiativeTransaction::TYPE_STATUS:
switch ($new) {
case FundInitiative::STATUS_OPEN:
return pht(
'%s reopened %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case FundInitiative::STATUS_CLOSED:
return pht(
'%s closed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
case FundInitiativeTransaction::TYPE_BACKER:
return pht(
'%s backed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
return parent::getTitleForFeed($story);
}
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case FundInitiativeTransaction::TYPE_DESCRIPTION:
case FundInitiativeTransaction::TYPE_RISKS:
return ($old === null);
}
return parent::shouldHide();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case FundInitiativeTransaction::TYPE_DESCRIPTION:
case FundInitiativeTransaction::TYPE_RISKS:
return ($this->getOldValue() !== null);
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
}
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
@@ -1,207 +1,208 @@
<?php
final class PhortuneCartCancelController
extends PhortuneCartController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->action = $data['action'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$cart = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->needPurchases(true)
->executeOne();
if (!$cart) {
return new Aphront404Response();
}
switch ($this->action) {
case 'cancel':
// You must be able to edit the account to cancel an order.
PhabricatorPolicyFilter::requireCapability(
$viewer,
$cart->getAccount(),
PhabricatorPolicyCapability::CAN_EDIT);
$is_refund = false;
break;
case 'refund':
// You must be able to control the merchant to refund an order.
PhabricatorPolicyFilter::requireCapability(
$viewer,
$cart->getMerchant(),
PhabricatorPolicyCapability::CAN_EDIT);
$is_refund = true;
break;
default:
return new Aphront404Response();
}
$cancel_uri = $cart->getDetailURI();
$merchant = $cart->getMerchant();
try {
if ($is_refund) {
$title = pht('Unable to Refund Order');
$cart->assertCanRefundOrder();
} else {
$title = pht('Unable to Cancel Order');
$cart->assertCanCancelOrder();
}
} catch (Exception $ex) {
return $this->newDialog()
->setTitle($title)
->appendChild($ex->getMessage())
->addCancelButton($cancel_uri);
}
$charges = id(new PhortuneChargeQuery())
->setViewer($viewer)
->withCartPHIDs(array($cart->getPHID()))
->withStatuses(
array(
PhortuneCharge::STATUS_HOLD,
PhortuneCharge::STATUS_CHARGED,
))
->execute();
$amounts = mpull($charges, 'getAmountAsCurrency');
$maximum = PhortuneCurrency::newFromList($amounts);
$v_refund = $maximum->formatForDisplay();
$errors = array();
$e_refund = true;
if ($request->isFormPost()) {
if ($is_refund) {
try {
$refund = PhortuneCurrency::newFromUserInput(
$viewer,
$request->getStr('refund'));
$refund->assertInRange('0.00 USD', $maximum->formatForDisplay());
} catch (Exception $ex) {
- $errors[] = $ex;
+ $errors[] = $ex->getMessage();
$e_refund = pht('Invalid');
}
} else {
$refund = $maximum;
}
if (!$errors) {
$charges = msort($charges, 'getID');
$charges = array_reverse($charges);
if ($charges) {
$providers = id(new PhortunePaymentProviderConfigQuery())
->setViewer($viewer)
->withPHIDs(mpull($charges, 'getProviderPHID'))
->execute();
$providers = mpull($providers, null, 'getPHID');
} else {
$providers = array();
}
foreach ($charges as $charge) {
$refundable = $charge->getAmountRefundableAsCurrency();
if (!$refundable->isPositive()) {
// This charge is a refund, or has already been fully refunded.
continue;
}
if ($refund->isGreaterThan($refundable)) {
$refund_amount = $refundable;
} else {
$refund_amount = $refund;
}
$provider_config = idx($providers, $charge->getProviderPHID());
if (!$provider_config) {
throw new Exception(pht('Unable to load provider for charge!'));
}
$provider = $provider_config->buildProvider();
$refund_charge = $cart->willRefundCharge(
$viewer,
$provider,
$charge,
$refund_amount);
$refunded = false;
try {
$provider->refundCharge($charge, $refund_charge);
$refunded = true;
} catch (Exception $ex) {
phlog($ex);
$cart->didFailRefund($charge, $refund_charge);
}
if ($refunded) {
$cart->didRefundCharge($charge, $refund_charge);
$refund = $refund->subtract($refund_amount);
}
if (!$refund->isPositive()) {
break;
}
}
if ($refund->isPositive()) {
throw new Exception(pht('Unable to refund some charges!'));
}
// TODO: If every HOLD and CHARGING transaction has been fully refunded
// and we're in a HOLD, REVIEW, PURCHASING or CHARGED cart state we
// probably need to kick the cart back to READY here (or maybe kill
// it if it was in REVIEW)?
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
}
if ($is_refund) {
$title = pht('Refund Order?');
$body = pht(
'Really refund this order?');
$button = pht('Refund Order');
$cancel_text = pht('Cancel');
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setName('refund')
->setLabel(pht('Amount'))
->setError($e_refund)
->setValue($v_refund));
$form = $form->buildLayoutView();
} else {
$title = pht('Cancel Order?');
$body = pht(
'Really cancel this order? Any payment will be refunded.');
$button = pht('Cancel Order');
// Don't give the user a "Cancel" button in response to a "Cancel?"
// prompt, as it's confusing.
$cancel_text = pht('Do Not Cancel Order');
$form = null;
}
return $this->newDialog()
->setTitle($title)
+ ->setErrors($errors)
->appendChild($body)
->appendChild($form)
->addSubmitButton($button)
->addCancelButton($cancel_uri, $cancel_text);
}
}
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
@@ -1,37 +1,38 @@
<?php
abstract class PhortuneProductImplementation {
abstract public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs);
abstract public function getRef();
abstract public function getName(PhortuneProduct $product);
abstract public function getPriceAsCurrency(PhortuneProduct $product);
protected function getContentSource() {
return PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_PHORTUNE,
array());
}
public function getPurchaseName(
PhortuneProduct $product,
PhortunePurchase $purchase) {
return $this->getName($product);
}
public function didPurchaseProduct(
PhortuneProduct $product,
PhortunePurchase $purchase) {
return;
}
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
@@ -1,588 +1,589 @@
<?php
final class PhortuneCart extends PhortuneDAO
implements PhabricatorPolicyInterface {
const STATUS_BUILDING = 'cart:building';
const STATUS_READY = 'cart:ready';
const STATUS_PURCHASING = 'cart:purchasing';
const STATUS_CHARGED = 'cart:charged';
const STATUS_HOLD = 'cart:hold';
const STATUS_REVIEW = 'cart:review';
const STATUS_PURCHASED = 'cart:purchased';
protected $accountPHID;
protected $authorPHID;
protected $merchantPHID;
protected $cartClass;
protected $status;
protected $metadata = array();
private $account = self::ATTACHABLE;
private $purchases = self::ATTACHABLE;
private $implementation = self::ATTACHABLE;
private $merchant = self::ATTACHABLE;
public static function initializeNewCart(
PhabricatorUser $actor,
PhortuneAccount $account,
PhortuneMerchant $merchant) {
$cart = id(new PhortuneCart())
->setAuthorPHID($actor->getPHID())
->setStatus(self::STATUS_BUILDING)
->setAccountPHID($account->getPHID())
->setMerchantPHID($merchant->getPHID());
$cart->account = $account;
$cart->purchases = array();
return $cart;
}
public function newPurchase(
PhabricatorUser $actor,
PhortuneProduct $product) {
$purchase = PhortunePurchase::initializeNewPurchase($actor, $product)
->setAccountPHID($this->getAccount()->getPHID())
->setCartPHID($this->getPHID())
->save();
$this->purchases[] = $purchase;
return $purchase;
}
public static function getStatusNameMap() {
return array(
self::STATUS_BUILDING => pht('Building'),
self::STATUS_READY => pht('Ready'),
self::STATUS_PURCHASING => pht('Purchasing'),
self::STATUS_CHARGED => pht('Charged'),
self::STATUS_HOLD => pht('Hold'),
self::STATUS_REVIEW => pht('Review'),
self::STATUS_PURCHASED => pht('Purchased'),
);
}
public static function getNameForStatus($status) {
return idx(self::getStatusNameMap(), $status, $status);
}
public function activateCart() {
$this->setStatus(self::STATUS_READY)->save();
return $this;
}
public function willApplyCharge(
PhabricatorUser $actor,
PhortunePaymentProvider $provider,
PhortunePaymentMethod $method = null) {
$account = $this->getAccount();
$charge = PhortuneCharge::initializeNewCharge()
->setAccountPHID($account->getPHID())
->setCartPHID($this->getPHID())
->setAuthorPHID($actor->getPHID())
->setMerchantPHID($this->getMerchant()->getPHID())
->setProviderPHID($provider->getProviderConfig()->getPHID())
->setAmountAsCurrency($this->getTotalPriceAsCurrency());
if ($method) {
$charge->setPaymentMethodPHID($method->getPHID());
}
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $this;
$copy->reload();
if ($copy->getStatus() !== self::STATUS_READY) {
throw new Exception(
pht(
'Cart has wrong status ("%s") to call willApplyCharge(), '.
'expected "%s".',
$copy->getStatus(),
self::STATUS_READY));
}
$charge->save();
$this->setStatus(PhortuneCart::STATUS_PURCHASING)->save();
$this->endReadLocking();
$this->saveTransaction();
return $charge;
}
public function didHoldCharge(PhortuneCharge $charge) {
$charge->setStatus(PhortuneCharge::STATUS_HOLD);
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $this;
$copy->reload();
if ($copy->getStatus() !== self::STATUS_PURCHASING) {
throw new Exception(
pht(
'Cart has wrong status ("%s") to call didHoldCharge(), '.
'expected "%s".',
$copy->getStatus(),
self::STATUS_PURCHASING));
}
$charge->save();
$this->setStatus(self::STATUS_HOLD)->save();
$this->endReadLocking();
$this->saveTransaction();
}
public function didApplyCharge(PhortuneCharge $charge) {
$charge->setStatus(PhortuneCharge::STATUS_CHARGED);
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $this;
$copy->reload();
if (($copy->getStatus() !== self::STATUS_PURCHASING) &&
($copy->getStatus() !== self::STATUS_HOLD)) {
throw new Exception(
pht(
'Cart has wrong status ("%s") to call didApplyCharge().',
$copy->getStatus()));
}
$charge->save();
$this->setStatus(self::STATUS_CHARGED)->save();
$this->endReadLocking();
$this->saveTransaction();
// TODO: Perform purchase review. Here, we would apply rules to determine
// whether the charge needs manual review (maybe making the decision via
// Herald, configuration, or by examining provider fraud data). For now,
// always require review.
$needs_review = true;
if ($needs_review) {
$this->willReviewCart();
} else {
$this->didReviewCart();
}
return $this;
}
public function willReviewCart() {
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $this;
$copy->reload();
if (($copy->getStatus() !== self::STATUS_CHARGED)) {
throw new Exception(
pht(
'Cart has wrong status ("%s") to call willReviewCart()!',
$copy->getStatus()));
}
$this->setStatus(self::STATUS_REVIEW)->save();
$this->endReadLocking();
$this->saveTransaction();
// TODO: Notify merchant to review order.
return $this;
}
public function didReviewCart() {
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $this;
$copy->reload();
if (($copy->getStatus() !== self::STATUS_CHARGED) &&
($copy->getStatus() !== self::STATUS_REVIEW)) {
throw new Exception(
pht(
'Cart has wrong status ("%s") to call didReviewCart()!',
$copy->getStatus()));
}
foreach ($this->purchases as $purchase) {
$purchase->getProduct()->didPurchaseProduct($purchase);
}
$this->setStatus(self::STATUS_PURCHASED)->save();
$this->endReadLocking();
$this->saveTransaction();
return $this;
}
public function didFailCharge(PhortuneCharge $charge) {
$charge->setStatus(PhortuneCharge::STATUS_FAILED);
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $this;
$copy->reload();
if (($copy->getStatus() !== self::STATUS_PURCHASING) &&
($copy->getStatus() !== self::STATUS_HOLD)) {
throw new Exception(
pht(
'Cart has wrong status ("%s") to call didFailCharge().',
$copy->getStatus()));
}
$charge->save();
// Move the cart back into STATUS_READY so the user can try
// making the purchase again.
$this->setStatus(self::STATUS_READY)->save();
$this->endReadLocking();
$this->saveTransaction();
return $this;
}
public function willRefundCharge(
PhabricatorUser $actor,
PhortunePaymentProvider $provider,
PhortuneCharge $charge,
PhortuneCurrency $amount) {
if (!$amount->isPositive()) {
throw new Exception(
pht('Trying to refund nonpositive amount of money!'));
}
if ($amount->isGreaterThan($charge->getAmountRefundableAsCurrency())) {
throw new Exception(
pht('Trying to refund more money than remaining on charge!'));
}
if ($charge->getRefundedChargePHID()) {
throw new Exception(
pht('Trying to refund a refund!'));
}
if (($charge->getStatus() !== PhortuneCharge::STATUS_CHARGED) &&
($charge->getStatus() !== PhortuneCharge::STATUS_HOLD)) {
throw new Exception(
pht('Trying to refund an uncharged charge!'));
}
$refund_charge = PhortuneCharge::initializeNewCharge()
->setAccountPHID($this->getAccount()->getPHID())
->setCartPHID($this->getPHID())
->setAuthorPHID($actor->getPHID())
->setMerchantPHID($this->getMerchant()->getPHID())
->setProviderPHID($provider->getProviderConfig()->getPHID())
->setPaymentMethodPHID($charge->getPaymentMethodPHID())
->setRefundedChargePHID($charge->getPHID())
->setAmountAsCurrency($amount->negate());
$charge->openTransaction();
$charge->beginReadLocking();
$copy = clone $charge;
$copy->reload();
if ($copy->getRefundingPHID() !== null) {
throw new Exception(
pht('Trying to refund a charge which is already refunding!'));
}
$refund_charge->save();
$charge->setRefundingPHID($refund_charge->getPHID());
$charge->save();
$charge->endReadLocking();
$charge->saveTransaction();
return $refund_charge;
}
public function didRefundCharge(
PhortuneCharge $charge,
PhortuneCharge $refund) {
$refund->setStatus(PhortuneCharge::STATUS_CHARGED);
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $charge;
$copy->reload();
if ($charge->getRefundingPHID() !== $refund->getPHID()) {
throw new Exception(
pht('Charge is in the wrong refunding state!'));
}
$charge->setRefundingPHID(null);
// NOTE: There's some trickiness here to get the signs right. Both
// these values are positive but the refund has a negative value.
$total_refunded = $charge
->getAmountRefundedAsCurrency()
->add($refund->getAmountAsCurrency()->negate());
$charge->setAmountRefundedAsCurrency($total_refunded);
$charge->save();
$refund->save();
$this->endReadLocking();
$this->saveTransaction();
+ $amount = $refund->getAmountAsCurrency()->negate();
foreach ($this->purchases as $purchase) {
- $purchase->getProduct()->didRefundProduct($purchase);
+ $purchase->getProduct()->didRefundProduct($purchase, $amount);
}
return $this;
}
public function didFailRefund(
PhortuneCharge $charge,
PhortuneCharge $refund) {
$refund->setStatus(PhortuneCharge::STATUS_FAILED);
$this->openTransaction();
$this->beginReadLocking();
$copy = clone $charge;
$copy->reload();
if ($charge->getRefundingPHID() !== $refund->getPHID()) {
throw new Exception(
pht('Charge is in the wrong refunding state!'));
}
$charge->setRefundingPHID(null);
$charge->save();
$refund->save();
$this->endReadLocking();
$this->saveTransaction();
}
public function getName() {
return $this->getImplementation()->getName($this);
}
public function getDoneURI() {
return $this->getImplementation()->getDoneURI($this);
}
public function getDoneActionName() {
return $this->getImplementation()->getDoneActionName($this);
}
public function getCancelURI() {
return $this->getImplementation()->getCancelURI($this);
}
public function getDetailURI() {
return '/phortune/cart/'.$this->getID().'/';
}
public function getCheckoutURI() {
return '/phortune/cart/'.$this->getID().'/checkout/';
}
public function canCancelOrder() {
try {
$this->assertCanCancelOrder();
return true;
} catch (Exception $ex) {
return false;
}
}
public function canRefundOrder() {
try {
$this->assertCanRefundOrder();
return true;
} catch (Exception $ex) {
return false;
}
}
public function assertCanCancelOrder() {
switch ($this->getStatus()) {
case self::STATUS_BUILDING:
throw new Exception(
pht(
'This order can not be cancelled because the application has not '.
'finished building it yet.'));
case self::STATUS_READY:
throw new Exception(
pht(
'This order can not be cancelled because it has not been placed.'));
}
return $this->getImplementation()->assertCanCancelOrder($this);
}
public function assertCanRefundOrder() {
switch ($this->getStatus()) {
case self::STATUS_BUILDING:
throw new Exception(
pht(
'This order can not be refunded because the application has not '.
'finished building it yet.'));
case self::STATUS_READY:
throw new Exception(
pht(
'This order can not be refunded because it has not been placed.'));
}
return $this->getImplementation()->assertCanRefundOrder($this);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'status' => 'text32',
'cartClass' => 'text128',
),
self::CONFIG_KEY_SCHEMA => array(
'key_account' => array(
'columns' => array('accountPHID'),
),
'key_merchant' => array(
'columns' => array('merchantPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhortuneCartPHIDType::TYPECONST);
}
public function attachPurchases(array $purchases) {
assert_instances_of($purchases, 'PhortunePurchase');
$this->purchases = $purchases;
return $this;
}
public function getPurchases() {
return $this->assertAttached($this->purchases);
}
public function attachAccount(PhortuneAccount $account) {
$this->account = $account;
return $this;
}
public function getAccount() {
return $this->assertAttached($this->account);
}
public function attachMerchant(PhortuneMerchant $merchant) {
$this->merchant = $merchant;
return $this;
}
public function getMerchant() {
return $this->assertAttached($this->merchant);
}
public function attachImplementation(
PhortuneCartImplementation $implementation) {
$this->implementation = $implementation;
return $this;
}
public function getImplementation() {
return $this->assertAttached($this->implementation);
}
public function getTotalPriceAsCurrency() {
$prices = array();
foreach ($this->getPurchases() as $purchase) {
$prices[] = $purchase->getTotalPriceAsCurrency();
}
return PhortuneCurrency::newFromList($prices);
}
public function setMetadataValue($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
public function getMetadataValue($key, $default = null) {
return idx($this->metadata, $key, $default);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
// NOTE: Both view and edit use the account's edit policy. We punch a hole
// through this for merchants, below.
return $this
->getAccount()
->getPolicy(PhabricatorPolicyCapability::CAN_EDIT);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) {
return true;
}
// If the viewer controls the merchant this order was placed with, they
// can view the order.
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
$can_admin = PhabricatorPolicyFilter::hasCapability(
$viewer,
$this->getMerchant(),
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_admin) {
return true;
}
}
return false;
}
public function describeAutomaticCapability($capability) {
return array(
pht('Orders inherit the policies of the associated account.'),
pht('The merchant you placed an order with can review and manage it.'),
);
}
}
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
@@ -1,107 +1,112 @@
<?php
/**
* A product is something users can purchase.
*/
final class PhortuneProduct extends PhortuneDAO
implements PhabricatorPolicyInterface {
protected $productClassKey;
protected $productClass;
protected $productRefKey;
protected $productRef;
protected $metadata = array();
private $implementation = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'productClassKey' => 'bytes12',
'productClass' => 'text128',
'productRefKey' => 'bytes12',
'productRef' => 'text128',
),
self::CONFIG_KEY_SCHEMA => array(
'key_product' => array(
'columns' => array('productClassKey', 'productRefKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhortuneProductPHIDType::TYPECONST);
}
public static function initializeNewProduct() {
return id(new PhortuneProduct());
}
public function attachImplementation(PhortuneProductImplementation $impl) {
$this->implementation = $impl;
}
public function getImplementation() {
return $this->assertAttached($this->implementation);
}
public function save() {
$this->productClassKey = PhabricatorHash::digestForIndex(
$this->productClass);
$this->productRefKey = PhabricatorHash::digestForIndex(
$this->productRef);
return parent::save();
}
public function getPriceAsCurrency() {
return $this->getImplementation()->getPriceAsCurrency($this);
}
public function getProductName() {
return $this->getImplementation()->getName($this);
}
public function getPurchaseName(PhortunePurchase $purchase) {
return $this->getImplementation()->getPurchaseName($this, $purchase);
}
public function didPurchaseProduct(PhortunePurchase $purchase) {
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);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 15:31 (3 w, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126023
Default Alt Text
(57 KB)

Event Timeline