1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 14:00:56 +01:00

Update Phortune payment account interfaces to handle merchant vs customer views

Summary: Depends on D20716. Ref T13366. This implements the new policy behavior cleanly in all top-level Phortune payment account interfaces.

Test Plan: As a merchant with an account relationship (not an account member) and an account member, browsed all account interfaces and attempted to perform edits. As a merchant, saw a reduced-strength view.

Maniphest Tasks: T13366

Differential Revision: https://secure.phabricator.com/D20717
This commit is contained in:
epriestley 2019-08-16 09:23:59 -07:00
parent a3213ab20b
commit 0cc7e8eeb8
19 changed files with 277 additions and 149 deletions

View file

@ -5222,7 +5222,6 @@ phutil_register_library_map(array(
'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php',
'PhortuneAccountBillingAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingAddressTransaction.php',
'PhortuneAccountBillingNameTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingNameTransaction.php',
'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php',
'PhortuneAccountChargesController' => 'applications/phortune/controller/account/PhortuneAccountChargesController.php',
'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php',
'PhortuneAccountDetailsController' => 'applications/phortune/controller/account/PhortuneAccountDetailsController.php',
@ -5277,6 +5276,7 @@ phutil_register_library_map(array(
'PhortuneCartUpdateController' => 'applications/phortune/controller/cart/PhortuneCartUpdateController.php',
'PhortuneCartViewController' => 'applications/phortune/controller/cart/PhortuneCartViewController.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
'PhortuneChargeListController' => 'applications/phortune/controller/charge/PhortuneChargeListController.php',
'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php',
'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php',
'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php',
@ -11769,10 +11769,9 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
),
'PhortuneAccountAddManagerController' => 'PhortuneController',
'PhortuneAccountAddManagerController' => 'PhortuneAccountController',
'PhortuneAccountBillingAddressTransaction' => 'PhortuneAccountTransactionType',
'PhortuneAccountBillingNameTransaction' => 'PhortuneAccountTransactionType',
'PhortuneAccountChargeListController' => 'PhortuneController',
'PhortuneAccountChargesController' => 'PhortuneAccountProfileController',
'PhortuneAccountController' => 'PhortuneController',
'PhortuneAccountDetailsController' => 'PhortuneAccountProfileController',
@ -11839,6 +11838,7 @@ phutil_register_library_map(array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
'PhortuneChargeListController' => 'PhortuneController',
'PhortuneChargePHIDType' => 'PhabricatorPHIDType',
'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine',

View file

@ -39,8 +39,6 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
'card/' => array(
'new/' => 'PhortunePaymentMethodCreateController',
),
'order/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneCartListController',
'subscription/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneSubscriptionListController',
@ -51,8 +49,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
'order/(?P<subscriptionID>\d+)/'
=> 'PhortuneCartListController',
),
'order/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneCartListController',
'charge/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneAccountChargeListController',
=> 'PhortuneChargeListController',
),
'card/(?P<id>\d+)/' => array(
'edit/' => 'PhortunePaymentMethodEditController',
@ -82,16 +82,12 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
),
'addresses/' => array(
'' => 'PhortuneAccountEmailAddressesController',
'(?P<id>\d+)/' => 'PhortuneAccountEmailViewController',
$this->getEditRoutePattern('edit/')
=> 'PhortuneAccountEmailEditController',
),
),
),
'address/' => array(
'(?P<id>\d+)/' => 'PhortuneAccountEmailViewController',
$this->getEditRoutePattern('edit/')
=> 'PhortuneAccountEmailEditController',
),
'product/' => array(
'' => 'PhortuneProductListController',
'view/(?P<id>\d+)/' => 'PhortuneProductViewController',

View file

@ -1,23 +1,17 @@
<?php
final class PhortuneAccountAddManagerController extends PhortuneController {
final class PhortuneAccountAddManagerController
extends PhortuneAccountController {
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountEditCapability() {
return true;
}
protected function handleAccountRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('accountID');
$account = $this->getAccount();
$account = id(new PhortuneAccountQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$id = $account->getID();
$v_managers = array();
$e_managers = null;
@ -53,12 +47,24 @@ final class PhortuneAccountAddManagerController extends PhortuneController {
}
}
$account_phid = $account->getPHID();
$handles = $viewer->loadHandles(array($account_phid));
$handle = $handles[$account_phid];
$form = id(new AphrontFormView())
->setUser($viewer)
->setViewer($viewer)
->appendInstructions(
pht(
'Choose one or more users to add as account managers. Managers '.
'have full control of the account.'))
->appendControl(
id(new AphrontFormStaticControl())
->setLabel(pht('Payment Account'))
->setValue($handle->renderLink()))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setLabel(pht('Managers'))
->setLabel(pht('Add Managers'))
->setName('managerPHIDs')
->setValue($v_managers)
->setError($e_managers));
@ -69,7 +75,6 @@ final class PhortuneAccountAddManagerController extends PhortuneController {
->setWidth(AphrontDialogView::WIDTH_FORM)
->addCancelButton($account_uri)
->addSubmitButton(pht('Add Managers'));
}
}

View file

@ -3,25 +3,27 @@
final class PhortuneAccountChargesController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Order History'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Order History'))
->setBorder(true);
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$charge_history = $this->buildChargeHistorySection($account);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$authority,
$charge_history,
));

View file

@ -4,14 +4,34 @@ abstract class PhortuneAccountController
extends PhortuneController {
private $account;
private $merchants;
protected function getAccount() {
return $this->account;
final public function handleRequest(AphrontRequest $request) {
if ($this->shouldRequireAccountEditCapability()) {
$response = $this->loadAccountForEdit();
} else {
$response = $this->loadAccountForView();
}
if ($response) {
return $response;
}
return $this->handleAccountRequest($request);
}
protected function setAccount(PhortuneAccount $account) {
$this->account = $account;
return $this;
abstract protected function shouldRequireAccountEditCapability();
abstract protected function handleAccountRequest(AphrontRequest $request);
final protected function getAccount() {
if ($this->account === null) {
throw new Exception(
pht(
'Unable to "getAccount()" before loading or setting account '.
'context.'));
}
return $this->account;
}
protected function buildApplicationCrumbs() {
@ -25,44 +45,112 @@ abstract class PhortuneAccountController
return $crumbs;
}
protected function loadAccount() {
// TODO: Currently, you must be able to edit an account to view the detail
// page, because the account must be broadly visible so merchants can
// process orders but merchants should not be able to see all the details
// of an account. Ideally the profile pages should be visible to merchants,
// too, just with less information.
return $this->loadAccountForEdit();
private function loadAccountForEdit() {
return $this->loadAccountWithCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
));
}
protected function loadAccountForEdit() {
private function loadAccountForView() {
return $this->loadAccountWithCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
));
}
private function loadAccountWithCapabilities(array $capabilities) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$account_id = $request->getURIData('accountID');
if (!$account_id) {
$account_id = $request->getURIData('id');
}
if (!$account_id) {
return new Aphront404Response();
throw new Exception(
pht(
'Controller ("%s") extends controller "%s", but is reachable '.
'with no "accountID" in URI.',
get_class($this),
__CLASS__));
}
$account = id(new PhortuneAccountQuery())
->setViewer($viewer)
->withIDs(array($account_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->requireCapabilities($capabilities)
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$this->account = $account;
$this->setAccount($account);
return null;
}
private function setAccount(PhortuneAccount $account) {
$this->account = $account;
$viewer = $this->getViewer();
if (!$account->isUserAccountMember($viewer)) {
$merchant_phids = $account->getMerchantPHIDs();
$merchants = id(new PhortuneMerchantQuery())
->setViewer($viewer)
->withPHIDs($merchant_phids)
->withMemberPHIDs(array($viewer->getPHID()))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
$this->merchants = $merchants;
} else {
$this->merchants = array();
}
return $this;
}
final protected function getMerchants() {
if ($this->merchants === null) {
throw new Exception(
pht(
'Unable to "getMerchants()" before loading or setting account '.
'context.'));
}
return $this->merchants;
}
final protected function newAccountAuthorityView() {
$viewer = $this->getViewer();
$merchants = $this->getMerchants();
if (!$merchants) {
return null;
}
$merchant_phids = mpull($merchants, 'getPHID');
$merchant_handles = $viewer->loadHandles($merchant_phids);
$merchant_handles = iterator_to_array($merchant_handles);
$merchant_list = mpull($merchant_handles, 'renderLink');
$merchant_list = phutil_implode_html(', ', $merchant_list);
$merchant_message = pht(
'You can view this account because you control %d merchant(s) it '.
'has a relationship with: %s.',
phutil_count($merchants),
$merchant_list);
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setErrors(
array(
$merchant_message,
));
}
}

View file

@ -3,12 +3,11 @@
final class PhortuneAccountDetailsController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return true;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
@ -26,6 +25,7 @@ final class PhortuneAccountDetailsController
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$details = $this->newDetailsView($account);
$curtain = $this->buildCurtainView($account);
@ -41,6 +41,7 @@ final class PhortuneAccountDetailsController
->setCurtain($curtain)
->setMainColumn(
array(
$authority,
$details,
$timeline,
));

View file

@ -3,25 +3,27 @@
final class PhortuneAccountEmailAddressesController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return true;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Email Addresses'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Email Addresses'))
->setBorder(true);
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$addresses = $this->buildAddressesSection($account);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$authority,
$addresses,
));

View file

@ -3,23 +3,17 @@
final class PhortuneAccountEmailEditController
extends PhortuneAccountController {
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountEditCapability() {
return true;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$engine = id(new PhortuneAccountEmailEditEngine())
->setController($this);
if (!$request->getURIData('id')) {
if (!$request->getURIData('accountID')) {
return new Aphront404Response();
}
$response = $this->loadAccount();
if ($response) {
return $response;
}
$account = $this->getAccount();
$engine->setAccount($account);
}

View file

@ -3,20 +3,23 @@
final class PhortuneAccountEmailViewController
extends PhortuneAccountController {
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountEditCapability() {
return true;
}
protected function handleAccountRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$account = $this->getAccount();
$address = id(new PhortuneAccountEmailQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$address) {
return new Aphront404Response();
}
$account = $address->getAccount();
$this->setAccount($account);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Email Addresses'), $account->getEmailAddressesURI())
->addTextCrumb($address->getObjectName())
@ -61,7 +64,8 @@ final class PhortuneAccountEmailViewController
$edit_uri = $this->getApplicationURI(
urisprintf(
'address/edit/%d/',
'account/%d/addresses/edit/%d/',
$account->getID(),
$address->getID()));
$curtain = $this->newCurtainView($account);

View file

@ -3,26 +3,29 @@
final class PhortuneAccountManagersController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Managers'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Managers'))
->setBorder(true);
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$members = $this->buildMembersSection($account);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$members,
));
->setFooter(
array(
$authority,
$members,
));
$navigation = $this->buildSideNavView('managers');

View file

@ -3,25 +3,28 @@
final class PhortuneAccountOrdersController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Order History'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Order History'))
->setBorder(true);
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$order_history = $this->newRecentOrdersView($account, 100);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$authority,
$order_history,
));

View file

@ -3,12 +3,11 @@
final class PhortuneAccountOverviewController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
@ -26,6 +25,7 @@ final class PhortuneAccountOverviewController
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$status = $this->buildStatusView($account, $invoices);
$invoices = $this->buildInvoicesSection($account, $invoices);
$purchase_history = $this->newRecentOrdersView($account, 10);
@ -34,6 +34,7 @@ final class PhortuneAccountOverviewController
->setHeader($header)
->setFooter(
array(
$authority,
$status,
$invoices,
$purchase_history,

View file

@ -3,18 +3,19 @@
final class PhortuneAccountPaymentMethodsController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Payment Methods'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Payment Methods'))
->setBorder(true);
$authority = $this->newAccountAuthorityView();
$header = $this->buildHeaderView();
$methods = $this->buildPaymentMethodsSection($account);
@ -22,6 +23,7 @@ final class PhortuneAccountPaymentMethodsController
->setHeader($header)
->setFooter(
array(
$authority,
$methods,
));

View file

@ -17,13 +17,16 @@ abstract class PhortuneAccountProfileController
->setHeader($title)
->setHeaderIcon('fa-user-circle');
return $header;
}
if ($this->getMerchants()) {
$customer_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setName(pht('Customer Account'))
->setColor('indigo')
->setIcon('fa-credit-card');
$header->addTag($customer_tag);
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->setBorder(true);
return $crumbs;
return $header;
}
protected function buildSideNavView($filter = null) {
@ -31,6 +34,8 @@ abstract class PhortuneAccountProfileController
$account = $this->getAccount();
$id = $account->getID();
$can_edit = !$this->getMerchants();
$nav = id(new AphrontSideNavFilterView())
->setBaseURI(new PhutilURI($this->getApplicationURI()));
@ -42,11 +47,12 @@ abstract class PhortuneAccountProfileController
$this->getApplicationURI("/{$id}/"),
'fa-user-circle');
$nav->addFilter(
'details',
pht('Account Details'),
$this->getApplicationURI("/account/{$id}/details/"),
'fa-address-card-o');
$nav->newLink('details')
->setName(pht('Account Details'))
->setHref($this->getApplicationURI("/account/{$id}/details/"))
->setIcon('fa-address-card-o')
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit);
$nav->addLabel(pht('Payments'));
@ -82,11 +88,12 @@ abstract class PhortuneAccountProfileController
$this->getApplicationURI("/account/{$id}/managers/"),
'fa-group');
$nav->addFilter(
'addresses',
pht('Email Addresses'),
$this->getApplicationURI("/account/{$id}/addresses/"),
'fa-envelope-o');
$nav->newLink('addresses')
->setname(pht('Email Addresses'))
->setHref($this->getApplicationURI("/account/{$id}/addresses/"))
->setIcon('fa-envelope-o')
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit);
$nav->selectFilter($filter);

View file

@ -3,26 +3,30 @@
final class PhortuneAccountSubscriptionController
extends PhortuneAccountProfileController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadAccount();
if ($response) {
return $response;
}
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$account = $this->getAccount();
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Subscriptions'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Subscriptions'))
->setBorder(true);
$header = $this->buildHeaderView();
$authority = $this->newAccountAuthorityView();
$subscriptions = $this->buildSubscriptionsSection($account);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$subscriptions,
));
->setFooter(
array(
$authority,
$subscriptions,
));
$navigation = $this->buildSideNavView('subscriptions');

View file

@ -1,6 +1,6 @@
<?php
final class PhortuneAccountChargeListController
final class PhortuneChargeListController
extends PhortuneController {
private $account;

View file

@ -138,6 +138,17 @@ final class PhortuneAccount extends PhortuneDAO
return $this;
}
public function isUserAccountMember(PhabricatorUser $user) {
$user_phid = $user->getPHID();
if (!$user_phid) {
return null;
}
$member_map = array_fuse($this->getMemberPHIDs());
return isset($member_map[$user_phid]);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
@ -174,8 +185,7 @@ final class PhortuneAccount extends PhortuneDAO
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
$members = array_fuse($this->getMemberPHIDs());
if (isset($members[$viewer->getPHID()])) {
if ($this->isUserAccountMember($viewer)) {
return true;
}

View file

@ -73,7 +73,8 @@ final class PhortuneAccountEmail
public function getURI() {
return urisprintf(
'/phortune/address/%d/',
'/phortune/account/%d/addresses/%d/',
$this->getAccount()->getID(),
$this->getID());
}

View file

@ -111,6 +111,11 @@ final class AphrontSideNavFilterView extends AphrontView {
$key, $name, $uri, PHUIListItemView::TYPE_BUTTON);
}
public function newLink($key) {
$this->addFilter($key, '');
return $this->getMenuView()->getItem($key);
}
private function addThing($key, $name, $uri, $type, $icon = null) {
$item = id(new PHUIListItemView())
->setName($name)