diff --git a/src/applications/fund/phortune/FundBackerCart.php b/src/applications/fund/phortune/FundBackerCart.php index 1d823ae03e..7196b21f48 100644 --- a/src/applications/fund/phortune/FundBackerCart.php +++ b/src/applications/fund/phortune/FundBackerCart.php @@ -1,80 +1,84 @@ 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 getName() { + return pht('Fund Initiative'); + } + public function willCreateCart( PhabricatorUser $viewer, PhortuneCart $cart) { $initiative = $this->getInitiative(); if (!$initiative) { throw new Exception( pht('Call setInitiative() before building a cart!')); } $cart->setMetadataValue('initiativePHID', $initiative->getPHID()); } public function loadImplementationsForCarts( PhabricatorUser $viewer, array $carts) { $phids = array(); foreach ($carts as $cart) { $phids[] = $cart->getMetadataValue('initiativePHID'); } $initiatives = id(new FundInitiativeQuery()) ->setViewer($viewer) ->withPHIDs($phids) ->execute(); $initiatives = mpull($initiatives, null, 'getPHID'); $objects = array(); foreach ($carts as $key => $cart) { $initiative_phid = $cart->getMetadataValue('initiativePHID'); $object = id(new FundBackerCart()) ->setInitiativePHID($initiative_phid); $initiative = idx($initiatives, $initiative_phid); if ($initiative) { $object->setInitiative($initiative); } $objects[$key] = $object; } return $objects; } public function getCancelURI(PhortuneCart $cart) { return '/'.$this->getInitiative()->getMonogram(); } public function getDoneURI(PhortuneCart $cart) { return '/'.$this->getInitiative()->getMonogram(); } } diff --git a/src/applications/fund/phortune/FundBackerProduct.php b/src/applications/fund/phortune/FundBackerProduct.php index 06f9f23675..a543ecb055 100644 --- a/src/applications/fund/phortune/FundBackerProduct.php +++ b/src/applications/fund/phortune/FundBackerProduct.php @@ -1,118 +1,118 @@ 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( - 'Back Initiative %s %s', + '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!')); } $xactions = array(); $xactions[] = id(new FundBackerTransaction()) ->setTransactionType(FundBackerTransaction::TYPE_STATUS) ->setNewValue(FundBacker::STATUS_PURCHASED); $editor = id(new FundBackerEditor()) ->setActor($viewer) ->setContentSource($this->getContentSource()); $editor->applyTransactions($backer, $xactions); $xactions = array(); $xactions[] = id(new FundInitiativeTransaction()) ->setTransactionType(FundInitiativeTransaction::TYPE_BACKER) ->setNewValue($backer->getPHID()); $editor = id(new FundInitiativeEditor()) ->setActor($viewer) ->setContentSource($this->getContentSource()); $editor->applyTransactions($this->getInitiative(), $xactions); return; } } diff --git a/src/applications/phortune/cart/PhortuneCartImplementation.php b/src/applications/phortune/cart/PhortuneCartImplementation.php index fe600d0028..43124dce41 100644 --- a/src/applications/phortune/cart/PhortuneCartImplementation.php +++ b/src/applications/phortune/cart/PhortuneCartImplementation.php @@ -1,22 +1,24 @@ accountID = $data['accountID']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $account = id(new PhortuneAccountQuery()) ->setViewer($user) ->withIDs(array($this->accountID)) ->executeOne(); if (!$account) { return new Aphront404Response(); } $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Account'), $request->getRequestURI()); $header = id(new PHUIHeaderView()) ->setHeader($title); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->setObjectURI($request->getRequestURI()) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Account')) ->setIcon('fa-pencil') ->setHref('#') ->setDisabled(true)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Members')) ->setIcon('fa-users') ->setHref('#') ->setDisabled(true)); $crumbs->setActionList($actions); $properties = id(new PHUIPropertyListView()) ->setObject($account) ->setUser($user); - $properties->addProperty(pht('Balance'), '-'); $properties->setActionList($actions); $payment_methods = $this->buildPaymentMethodsSection($account); $purchase_history = $this->buildPurchaseHistorySection($account); $charge_history = $this->buildChargeHistorySection($account); $account_history = $this->buildAccountHistorySection($account); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); return $this->buildApplicationPage( array( $crumbs, $object_box, $payment_methods, $purchase_history, $charge_history, $account_history, ), array( 'title' => $title, )); } private function buildPaymentMethodsSection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $account, PhabricatorPolicyCapability::CAN_EDIT); $id = $account->getID(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Payment Methods')); $list = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setNoDataString( pht('No payment methods associated with this account.')); $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->execute(); if ($methods) { $this->loadHandles(mpull($methods, 'getAuthorPHID')); } foreach ($methods as $method) { $id = $method->getID(); $item = new PHUIObjectItemView(); $item->setHeader($method->getFullDisplayName()); switch ($method->getStatus()) { case PhortunePaymentMethod::STATUS_ACTIVE: $item->setBarColor('green'); $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-times') ->setHref($disable_uri) ->setDisabled(!$can_edit) ->setWorkflow(true)); break; case PhortunePaymentMethod::STATUS_DISABLED: $item->setDisabled(true); break; } $provider = $method->buildPaymentProvider(); $item->addAttribute($provider->getPaymentMethodProviderDescription()); $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-pencil') ->setHref($edit_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $list->addItem($item); } return id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($list); } private function buildPurchaseHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); $carts = id(new PhortuneCartQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->needPurchases(true) ->withStatuses( array( PhortuneCart::STATUS_PURCHASING, PhortuneCart::STATUS_PURCHASED, )) ->execute(); + $phids = array(); + foreach ($carts as $cart) { + $phids[] = $cart->getPHID(); + foreach ($cart->getPurchases() as $purchase) { + $phids[] = $purchase->getPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + $rows = array(); $rowc = array(); foreach ($carts as $cart) { - $cart_link = phutil_tag( - 'a', - array( - 'href' => $this->getApplicationURI('cart/'.$cart->getID().'/'), - ), - pht('Cart %d', $cart->getID())); + $cart_link = $handles[$cart->getPHID()]->renderLink(); + $purchases = $cart->getPurchases(); + + if (count($purchases) == 1) { + $purchase_name = $handles[$purchase->getPHID()]->renderLink(); + $purchases = array(); + } else { + $purchase_name = ''; + } - $rowc[] = 'highlighted'; + $rowc[] = ''; $rows[] = array( - phutil_tag('strong', array(), $cart_link), - '', - '', + phutil_tag( + 'strong', + array(), + $cart_link), + $purchase_name, + phutil_tag( + 'strong', + array(), + $cart->getTotalPriceAsCurrency()->formatForDisplay()), + phabricator_datetime($cart->getDateModified(), $viewer), ); - foreach ($cart->getPurchases() as $purchase) { + foreach ($purchases as $purchase) { $id = $purchase->getID(); $price = $purchase->getTotalPriceAsCurrency()->formatForDisplay(); - $purchase_link = phutil_tag( - 'a', - array( - 'href' => $this->getApplicationURI('purchase/'.$id.'/'), - ), - $purchase->getFullDisplayName()); - $rowc[] = ''; $rows[] = array( '', - $purchase_link, + $handles[$purchase->getPHID()]->renderLink(), $price, + '', ); } } $table = id(new AphrontTableView($rows)) ->setRowClasses($rowc) ->setHeaders( array( pht('Cart'), pht('Purchase'), pht('Amount'), + pht('Updated'), )) ->setColumnClasses( array( '', 'wide', 'right', + 'right', )); $header = id(new PHUIHeaderView()) ->setHeader(pht('Purchase History')); return id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($table); } private function buildChargeHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); $charges = id(new PhortuneChargeQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) ->needCarts(true) ->execute(); return $this->buildChargesTable($charges); } private function buildAccountHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $user = $request->getUser(); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Account History')); - $xactions = id(new PhortuneAccountTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($account->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setObjectPHID($account->getPHID()) ->setTransactions($xactions) ->setMarkupEngine($engine); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header); - - return array( - $box, - $xaction_view, - ); + return $xaction_view; } } diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php index 87bd6eef7b..21b23e2c7b 100644 --- a/src/applications/phortune/controller/PhortuneController.php +++ b/src/applications/phortune/controller/PhortuneController.php @@ -1,135 +1,138 @@ getRequest())); } protected function buildChargesTable(array $charges, $show_cart = true) { $request = $this->getRequest(); $viewer = $request->getUser(); - $rows = array(); + $phids = array(); foreach ($charges as $charge) { - $cart = $charge->getCart(); - $cart_id = $cart->getID(); - $cart_uri = $this->getApplicationURI("cart/{$cart_id}/"); - $cart_href = phutil_tag( - 'a', - array( - 'href' => $cart_uri, - ), - pht('Cart %d', $cart_id)); + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + + $handles = $this->loadViewerHandles($phids); + $rows = array(); + foreach ($charges as $charge) { $rows[] = array( $charge->getID(), - $cart_href, - $charge->getProviderPHID(), - $charge->getPaymentMethodPHID(), + $handles[$charge->getCartPHID()]->renderLink(), + $handles[$charge->getProviderPHID()]->renderLink(), + $handles[$charge->getPaymentMethodPHID()]->renderLink(), + $handles[$charge->getMerchantPHID()]->renderLink(), $charge->getAmountAsCurrency()->formatForDisplay(), - $charge->getStatus(), + PhortuneCharge::getNameForStatus($charge->getStatus()), phabricator_datetime($charge->getDateCreated(), $viewer), ); } $charge_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('ID'), pht('Cart'), pht('Provider'), pht('Method'), + pht('Merchant'), pht('Amount'), pht('Status'), pht('Created'), )) ->setColumnClasses( array( '', - 'strong', + '', + '', '', '', 'wide right', '', '', )) ->setColumnVisibility( array( true, $show_cart, )); $header = id(new PHUIHeaderView()) ->setHeader(pht('Charge History')); return id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($charge_table); } protected function addAccountCrumb( $crumbs, PhortuneAccount $account, $link = true) { $name = pht('Account'); $href = null; if ($link) { $href = $this->getApplicationURI($account->getID().'/'); $crumbs->addTextCrumb($name, $href); } else { $crumbs->addTextCrumb($name); } } private function loadEnabledProvidersForMerchant(PhortuneMerchant $merchant) { $viewer = $this->getRequest()->getUser(); $provider_configs = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) ->withMerchantPHIDs(array($merchant->getPHID())) ->execute(); $providers = mpull($provider_configs, 'buildProvider', 'getID'); foreach ($providers as $key => $provider) { if (!$provider->isEnabled()) { unset($providers[$key]); } } return $providers; } protected function loadCreatePaymentMethodProvidersForMerchant( PhortuneMerchant $merchant) { $providers = $this->loadEnabledProvidersForMerchant($merchant); foreach ($providers as $key => $provider) { if (!$provider->canCreatePaymentMethods()) { unset($providers[$key]); continue; } } return $providers; } protected function loadOneTimePaymentProvidersForMerchant( PhortuneMerchant $merchant) { $providers = $this->loadEnabledProvidersForMerchant($merchant); foreach ($providers as $key => $provider) { if (!$provider->canProcessOneTimePayments()) { unset($providers[$key]); continue; } } return $providers; } } diff --git a/src/applications/phortune/phid/PhortuneCartPHIDType.php b/src/applications/phortune/phid/PhortuneCartPHIDType.php index 344286a351..c9488d84ab 100644 --- a/src/applications/phortune/phid/PhortuneCartPHIDType.php +++ b/src/applications/phortune/phid/PhortuneCartPHIDType.php @@ -1,38 +1,39 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $cart = $objects[$phid]; $id = $cart->getID(); + $name = $cart->getImplementation()->getName(); - $handle->setName(pht('Cart %d', $id)); + $handle->setName($name); $handle->setURI("/phortune/cart/{$id}/"); } } } diff --git a/src/applications/phortune/phid/PhortuneMerchantPHIDType.php b/src/applications/phortune/phid/PhortuneMerchantPHIDType.php index 143a970fbf..a1b8b73871 100644 --- a/src/applications/phortune/phid/PhortuneMerchantPHIDType.php +++ b/src/applications/phortune/phid/PhortuneMerchantPHIDType.php @@ -1,38 +1,38 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $merchant = $objects[$phid]; $id = $merchant->getID(); - $handle->setName(pht('Merchant %d %s', $id, $merchant->getName())); + $handle->setName($merchant->getName()); $handle->setURI("/phortune/merchant/{$id}/"); } } } diff --git a/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php b/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php index 6e334b66b3..34501526b2 100644 --- a/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php +++ b/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php @@ -1,38 +1,38 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $method = $objects[$phid]; $id = $method->getID(); - $handle->setName(pht('Method %d', $id)); + $handle->setName($method->getFullDisplayName()); $handle->setURI("/phortune/method/{$id}/"); } } } diff --git a/src/applications/phortune/phid/PhortunePaymentProviderPHIDType.php b/src/applications/phortune/phid/PhortunePaymentProviderPHIDType.php index 55be6e460c..7391e71108 100644 --- a/src/applications/phortune/phid/PhortunePaymentProviderPHIDType.php +++ b/src/applications/phortune/phid/PhortunePaymentProviderPHIDType.php @@ -1,38 +1,37 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $provider_config = $objects[$phid]; $id = $provider_config->getID(); - $handle->setName(pht('Payment Provider %d', $id)); - $handle->setURI("/phortune/provider/{$id}/"); + $handle->setName($provider_config->buildProvider()->getName()); } } } diff --git a/src/applications/phortune/phid/PhortunePurchasePHIDType.php b/src/applications/phortune/phid/PhortunePurchasePHIDType.php index f80e191691..5018a47164 100644 --- a/src/applications/phortune/phid/PhortunePurchasePHIDType.php +++ b/src/applications/phortune/phid/PhortunePurchasePHIDType.php @@ -1,38 +1,38 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $purchase = $objects[$phid]; $id = $purchase->getID(); - $handle->setName(pht('Purchase %d', $id)); + $handle->setName($purchase->getFullDisplayName()); $handle->setURI("/phortune/purchase/{$id}/"); } } } diff --git a/src/applications/phortune/storage/PhortuneCharge.php b/src/applications/phortune/storage/PhortuneCharge.php index 6386c3db7e..3d708416b6 100644 --- a/src/applications/phortune/storage/PhortuneCharge.php +++ b/src/applications/phortune/storage/PhortuneCharge.php @@ -1,120 +1,132 @@ setStatus(self::STATUS_CHARGING); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'metadata' => self::SERIALIZATION_JSON, ), self::CONFIG_APPLICATION_SERIALIZERS => array( 'amountAsCurrency' => new PhortuneCurrencySerializer(), ), self::CONFIG_COLUMN_SCHEMA => array( 'paymentProviderKey' => 'text128', 'paymentMethodPHID' => 'phid?', 'amountAsCurrency' => 'text64', 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'key_cart' => array( 'columns' => array('cartPHID'), ), 'key_account' => array( 'columns' => array('accountPHID'), ), 'key_merchant' => array( 'columns' => array('merchantPHID'), ), 'key_provider' => array( 'columns' => array('providerPHID'), ), ), ) + parent::getConfiguration(); } + public static function getStatusNameMap() { + return array( + self::STATUS_CHARGING => pht('Charging'), + self::STATUS_CHARGED => pht('Charged'), + self::STATUS_FAILED => pht('Failed'), + ); + } + + public static function getNameForStatus($status) { + return idx(self::getStatusNameMap(), $status, pht('Unknown')); + } + public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhortuneChargePHIDType::TYPECONST); } public function getMetadataValue($key, $default = null) { return idx($this->metadata, $key, $default); } public function setMetadataValue($key, $value) { $this->metadata[$key] = $value; return $this; } public function getAccount() { return $this->assertAttached($this->account); } public function attachAccount(PhortuneAccount $account) { $this->account = $account; return $this; } public function getCart() { return $this->assertAttached($this->cart); } public function attachCart(PhortuneCart $cart = null) { $this->cart = $cart; return $this; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return $this->getAccount()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getAccount()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('Charges inherit the policies of the associated account.'); } } diff --git a/src/applications/phortune/storage/PhortunePurchase.php b/src/applications/phortune/storage/PhortunePurchase.php index 98e6a61c5d..538df69082 100644 --- a/src/applications/phortune/storage/PhortunePurchase.php +++ b/src/applications/phortune/storage/PhortunePurchase.php @@ -1,124 +1,125 @@ setAuthorPHID($actor->getPHID()) ->setProductPHID($product->getPHID()) ->setQuantity(1) ->setStatus(self::STATUS_PENDING) ->setBasePriceAsCurrency($product->getPriceAsCurrency()); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'metadata' => self::SERIALIZATION_JSON, ), self::CONFIG_APPLICATION_SERIALIZERS => array( 'basePriceAsCurrency' => new PhortuneCurrencySerializer(), ), self::CONFIG_COLUMN_SCHEMA => array( 'cartPHID' => 'phid?', 'basePriceAsCurrency' => 'text64', 'quantity' => 'uint32', 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'key_cart' => array( 'columns' => array('cartPHID'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { - return PhabricatorPHID::generateNewPHID(PhortuneChargePHIDType::TYPECONST); + return PhabricatorPHID::generateNewPHID( + PhortunePurchasePHIDType::TYPECONST); } public function attachCart(PhortuneCart $cart) { $this->cart = $cart; return $this; } public function getCart() { return $this->assertAttached($this->cart); } public function attachProduct(PhortuneProduct $product) { $this->product = $product; return $this; } public function getProduct() { return $this->assertAttached($this->product); } public function getFullDisplayName() { return $this->getProduct()->getPurchaseName($this); } public function getTotalPriceAsCurrency() { return $this->getBasePriceAsCurrency(); } public function getMetadataValue($key, $default = null) { return idx($this->metadata, $key, $default); } public function setMetadataValue($key, $value) { $this->metadata[$key] = $value; return $this; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getCart()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getCart()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('Purchases have the policies of their cart.'); } }